Go back to index | PHP CodeBrowser

phpseclib/Net/SFTP.php
  1.    1  <?php
  2.    2 
  3.    3 /**
  4.    4  * Pure-PHP implementation of SFTP.
  5.    5  *
  6.    6  * PHP versions 4 and 5
  7.    7  *
  8.    8  * Currently only supports SFTPv2 and v3, which, according to wikipedia.org, "is the most widely used version,
  9.    9  * implemented by the popular OpenSSH SFTP server".  If you want SFTPv4/5/6 support, provide me with access
  10.   10  * to an SFTPv4/5/6 server.
  11.   11  *
  12.   12  * The API for this library is modeled after the API from PHP's {@link http://php.net/book.ftp FTP extension}.
  13.   13  *
  14.   14  * Here's a short example of how to use this library:
  15.   15  * <code>
  16.   16  * <?php
  17.   17  *    include 'Net/SFTP.php';
  18.   18  *
  19.   19  *    $sftp = new Net_SFTP('www.domain.tld');
  20.   20  *    if (!$sftp->login('username', 'password')) {
  21.   21  *        exit('Login Failed');
  22.   22  *    }
  23.   23  *
  24.   24  *    echo $sftp->pwd() . "\r\n";
  25.   25  *    $sftp->put('filename.ext', 'hello, world!');
  26.   26  *    print_r($sftp->nlist());
  27.   27  * ?>
  28.   28  * </code>
  29.   29  *
  30.   30  * LICENSE: Permission is hereby granted, free of charge, to any person obtaining a copy
  31.   31  * of this software and associated documentation files (the "Software"), to deal
  32.   32  * in the Software without restriction, including without limitation the rights
  33.   33  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  34.   34  * copies of the Software, and to permit persons to whom the Software is
  35.   35  * furnished to do so, subject to the following conditions:
  36.   36  *
  37.   37  * The above copyright notice and this permission notice shall be included in
  38.   38  * all copies or substantial portions of the Software.
  39.   39  *
  40.   40  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  41.   41  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  42.   42  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  43.   43  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  44.   44  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  45.   45  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  46.   46  * THE SOFTWARE.
  47.   47  *
  48.   48  * @category  Net
  49.   49  * @package   Net_SFTP
  50.   50  * @author    Jim Wigginton <terrafrost@php.net>
  51.   51  * @copyright 2009 Jim Wigginton
  52.   52  * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
  53.   53  * @link      http://phpseclib.sourceforge.net
  54.   54  */
  55.   55 
  56.   56 /**
  57.   57  * Include Net_SSH2
  58.   58  */
  59.   59 if (!class_exists('Net_SSH2')) {
  60.   60     include_once 'SSH2.php';
  61.   61 }
  62.   62 
  63.   63 /**#@+
  64.   64  * @access public
  65.   65  * @see self::getLog()
  66.   66  */
  67.   67 /**
  68.   68  * Returns the message numbers
  69.   69  */
  70.   70 define('NET_SFTP_LOG_SIMPLE',  NET_SSH2_LOG_SIMPLE);
  71.   71 /**
  72.   72  * Returns the message content
  73.   73  */
  74.   74 define('NET_SFTP_LOG_COMPLEX'NET_SSH2_LOG_COMPLEX);
  75.   75 /**
  76.   76  * Outputs the message content in real-time.
  77.   77  */
  78.   78 define('NET_SFTP_LOG_REALTIME'3);
  79.   79 /**#@-*/
  80.   80 
  81.   81 /**
  82.   82  * SFTP channel constant
  83.   83  *
  84.   84  * Net_SSH2::exec() uses 0 and Net_SSH2::read() / Net_SSH2::write() use 1.
  85.   85  *
  86.   86  * @see Net_SSH2::_send_channel_packet()
  87.   87  * @see Net_SSH2::_get_channel_packet()
  88.   88  * @access private
  89.   89  */
  90.   90 define('NET_SFTP_CHANNEL'0x100);
  91.   91 
  92.   92 /**#@+
  93.   93  * @access public
  94.   94  * @see self::put()
  95.   95  */
  96.   96 /**
  97.   97  * Reads data from a local file.
  98.   98  */
  99.   99 define('NET_SFTP_LOCAL_FILE',    1);
  100.  100 /**
  101.  101  * Reads data from a string.
  102.  102  */
  103.  103 // this value isn't really used anymore but i'm keeping it reserved for historical reasons
  104.  104 define('NET_SFTP_STRING',        2);
  105.  105 /**
  106.  106  * Reads data from callback:
  107.  107  * function callback($length) returns string to proceed, null for EOF
  108.  108  */
  109.  109 define('NET_SFTP_CALLBACK',     16);
  110.  110 /**
  111.  111  * Resumes an upload
  112.  112  */
  113.  113 define('NET_SFTP_RESUME',        4);
  114.  114 /**
  115.  115  * Append a local file to an already existing remote file
  116.  116  */
  117.  117 define('NET_SFTP_RESUME_START',  8);
  118.  118 /**#@-*/
  119.  119 
  120.  120 /**
  121.  121  * Pure-PHP implementations of SFTP.
  122.  122  *
  123.  123  * @package Net_SFTP
  124.  124  * @author  Jim Wigginton <terrafrost@php.net>
  125.  125  * @access  public
  126.  126  */
  127.  127 class Net_SFTP extends Net_SSH2
  128.  128 {
  129.  129     /**
  130.  130      * Packet Types
  131.  131      *
  132.  132      * @see self::Net_SFTP()
  133.  133      * @var array
  134.  134      * @access private
  135.  135      */
  136.  136     var $packet_types = array();
  137.  137 
  138.  138     /**
  139.  139      * Status Codes
  140.  140      *
  141.  141      * @see self::Net_SFTP()
  142.  142      * @var array
  143.  143      * @access private
  144.  144      */
  145.  145     var $status_codes = array();
  146.  146 
  147.  147     /**
  148.  148      * The Request ID
  149.  149      *
  150.  150      * The request ID exists in the off chance that a packet is sent out-of-order.  Of course, this library doesn't support
  151.  151      * concurrent actions, so it's somewhat academic, here.
  152.  152      *
  153.  153      * @var int
  154.  154      * @see self::_send_sftp_packet()
  155.  155      * @access private
  156.  156      */
  157.  157     var $request_id false;
  158.  158 
  159.  159     /**
  160.  160      * The Packet Type
  161.  161      *
  162.  162      * The request ID exists in the off chance that a packet is sent out-of-order.  Of course, this library doesn't support
  163.  163      * concurrent actions, so it's somewhat academic, here.
  164.  164      *
  165.  165      * @var int
  166.  166      * @see self::_get_sftp_packet()
  167.  167      * @access private
  168.  168      */
  169.  169     var $packet_type = -1;
  170.  170 
  171.  171     /**
  172.  172      * Packet Buffer
  173.  173      *
  174.  174      * @var string
  175.  175      * @see self::_get_sftp_packet()
  176.  176      * @access private
  177.  177      */
  178.  178     var $packet_buffer '';
  179.  179 
  180.  180     /**
  181.  181      * Extensions supported by the server
  182.  182      *
  183.  183      * @var array
  184.  184      * @see self::_initChannel()
  185.  185      * @access private
  186.  186      */
  187.  187     var $extensions = array();
  188.  188 
  189.  189     /**
  190.  190      * Server SFTP version
  191.  191      *
  192.  192      * @var int
  193.  193      * @see self::_initChannel()
  194.  194      * @access private
  195.  195      */
  196.  196     var $version;
  197.  197 
  198.  198     /**
  199.  199      * Current working directory
  200.  200      *
  201.  201      * @var string
  202.  202      * @see self::realpath()
  203.  203      * @see self::chdir()
  204.  204      * @access private
  205.  205      */
  206.  206     var $pwd false;
  207.  207 
  208.  208     /**
  209.  209      * Packet Type Log
  210.  210      *
  211.  211      * @see self::getLog()
  212.  212      * @var array
  213.  213      * @access private
  214.  214      */
  215.  215     var $packet_type_log = array();
  216.  216 
  217.  217     /**
  218.  218      * Packet Log
  219.  219      *
  220.  220      * @see self::getLog()
  221.  221      * @var array
  222.  222      * @access private
  223.  223      */
  224.  224     var $packet_log = array();
  225.  225 
  226.  226     /**
  227.  227      * Error information
  228.  228      *
  229.  229      * @see self::getSFTPErrors()
  230.  230      * @see self::getLastSFTPError()
  231.  231      * @var array
  232.  232      * @access private
  233.  233      */
  234.  234     var $sftp_errors = array();
  235.  235 
  236.  236     /**
  237.  237      * Stat Cache
  238.  238      *
  239.  239      * Rather than always having to open a directory and close it immediately there after to see if a file is a directory
  240.  240      * we'll cache the results.
  241.  241      *
  242.  242      * @see self::_update_stat_cache()
  243.  243      * @see self::_remove_from_stat_cache()
  244.  244      * @see self::_query_stat_cache()
  245.  245      * @var array
  246.  246      * @access private
  247.  247      */
  248.  248     var $stat_cache = array();
  249.  249 
  250.  250     /**
  251.  251      * Max SFTP Packet Size
  252.  252      *
  253.  253      * @see self::Net_SFTP()
  254.  254      * @see self::get()
  255.  255      * @var array
  256.  256      * @access private
  257.  257      */
  258.  258     var $max_sftp_packet;
  259.  259 
  260.  260     /**
  261.  261      * Stat Cache Flag
  262.  262      *
  263.  263      * @see self::disableStatCache()
  264.  264      * @see self::enableStatCache()
  265.  265      * @var bool
  266.  266      * @access private
  267.  267      */
  268.  268     var $use_stat_cache true;
  269.  269 
  270.  270     /**
  271.  271      * Sort Options
  272.  272      *
  273.  273      * @see self::_comparator()
  274.  274      * @see self::setListOrder()
  275.  275      * @var array
  276.  276      * @access private
  277.  277      */
  278.  278     var $sortOptions = array();
  279.  279 
  280.  280     /**
  281.  281      * Canonicalization Flag
  282.  282      *
  283.  283      * Determines whether or not paths should be canonicalized before being
  284.  284      * passed on to the remote server.
  285.  285      *
  286.  286      * @see self::enablePathCanonicalization()
  287.  287      * @see self::disablePathCanonicalization()
  288.  288      * @see self::realpath()
  289.  289      * @var bool
  290.  290      * @access private
  291.  291      */
  292.  292     var $canonicalize_paths true;
  293.  293 
  294.  294     /**
  295.  295      * Default Constructor.
  296.  296      *
  297.  297      * Connects to an SFTP server
  298.  298      *
  299.  299      * @param string $host
  300.  300      * @param int $port
  301.  301      * @param int $timeout
  302.  302      * @return Net_SFTP
  303.  303      * @access public
  304.  304      */
  305.  305     function __construct($host$port 22$timeout 10)
  306.  306     {
  307.  307         parent::__construct($host$port$timeout);
  308.  308 
  309.  309         $this->max_sftp_packet << 15;
  310.  310 
  311.  311         $this->packet_types = array(
  312.  312             1  => 'NET_SFTP_INIT',
  313.  313             2  => 'NET_SFTP_VERSION',
  314.  314             /* the format of SSH_FXP_OPEN changed between SFTPv4 and SFTPv5+:
  315.  315                    SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.1
  316.  316                pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3 */
  317.  317             3  => 'NET_SFTP_OPEN',
  318.  318             4  => 'NET_SFTP_CLOSE',
  319.  319             5  => 'NET_SFTP_READ',
  320.  320             6  => 'NET_SFTP_WRITE',
  321.  321             7  => 'NET_SFTP_LSTAT',
  322.  322             9  => 'NET_SFTP_SETSTAT',
  323.  323             11 => 'NET_SFTP_OPENDIR',
  324.  324             12 => 'NET_SFTP_READDIR',
  325.  325             13 => 'NET_SFTP_REMOVE',
  326.  326             14 => 'NET_SFTP_MKDIR',
  327.  327             15 => 'NET_SFTP_RMDIR',
  328.  328             16 => 'NET_SFTP_REALPATH',
  329.  329             17 => 'NET_SFTP_STAT',
  330.  330             /* the format of SSH_FXP_RENAME changed between SFTPv4 and SFTPv5+:
  331.  331                    SFTPv5+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
  332.  332                pre-SFTPv5 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.5 */
  333.  333             18 => 'NET_SFTP_RENAME',
  334.  334             19 => 'NET_SFTP_READLINK',
  335.  335             20 => 'NET_SFTP_SYMLINK',
  336.  336 
  337.  337             101=> 'NET_SFTP_STATUS',
  338.  338             102=> 'NET_SFTP_HANDLE',
  339.  339             /* the format of SSH_FXP_NAME changed between SFTPv3 and SFTPv4+:
  340.  340                    SFTPv4+: http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.4
  341.  341                pre-SFTPv4 : http://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-7 */
  342.  342             103=> 'NET_SFTP_DATA',
  343.  343             104=> 'NET_SFTP_NAME',
  344.  344             105=> 'NET_SFTP_ATTRS',
  345.  345 
  346.  346             200=> 'NET_SFTP_EXTENDED'
  347.  347         );
  348.  348         $this->status_codes = array(
  349.  349             => 'NET_SFTP_STATUS_OK',
  350.  350             => 'NET_SFTP_STATUS_EOF',
  351.  351             => 'NET_SFTP_STATUS_NO_SUCH_FILE',
  352.  352             => 'NET_SFTP_STATUS_PERMISSION_DENIED',
  353.  353             => 'NET_SFTP_STATUS_FAILURE',
  354.  354             => 'NET_SFTP_STATUS_BAD_MESSAGE',
  355.  355             => 'NET_SFTP_STATUS_NO_CONNECTION',
  356.  356             => 'NET_SFTP_STATUS_CONNECTION_LOST',
  357.  357             => 'NET_SFTP_STATUS_OP_UNSUPPORTED',
  358.  358             => 'NET_SFTP_STATUS_INVALID_HANDLE',
  359.  359             10 => 'NET_SFTP_STATUS_NO_SUCH_PATH',
  360.  360             11 => 'NET_SFTP_STATUS_FILE_ALREADY_EXISTS',
  361.  361             12 => 'NET_SFTP_STATUS_WRITE_PROTECT',
  362.  362             13 => 'NET_SFTP_STATUS_NO_MEDIA',
  363.  363             14 => 'NET_SFTP_STATUS_NO_SPACE_ON_FILESYSTEM',
  364.  364             15 => 'NET_SFTP_STATUS_QUOTA_EXCEEDED',
  365.  365             16 => 'NET_SFTP_STATUS_UNKNOWN_PRINCIPAL',
  366.  366             17 => 'NET_SFTP_STATUS_LOCK_CONFLICT',
  367.  367             18 => 'NET_SFTP_STATUS_DIR_NOT_EMPTY',
  368.  368             19 => 'NET_SFTP_STATUS_NOT_A_DIRECTORY',
  369.  369             20 => 'NET_SFTP_STATUS_INVALID_FILENAME',
  370.  370             21 => 'NET_SFTP_STATUS_LINK_LOOP',
  371.  371             22 => 'NET_SFTP_STATUS_CANNOT_DELETE',
  372.  372             23 => 'NET_SFTP_STATUS_INVALID_PARAMETER',
  373.  373             24 => 'NET_SFTP_STATUS_FILE_IS_A_DIRECTORY',
  374.  374             25 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_CONFLICT',
  375.  375             26 => 'NET_SFTP_STATUS_BYTE_RANGE_LOCK_REFUSED',
  376.  376             27 => 'NET_SFTP_STATUS_DELETE_PENDING',
  377.  377             28 => 'NET_SFTP_STATUS_FILE_CORRUPT',
  378.  378             29 => 'NET_SFTP_STATUS_OWNER_INVALID',
  379.  379             30 => 'NET_SFTP_STATUS_GROUP_INVALID',
  380.  380             31 => 'NET_SFTP_STATUS_NO_MATCHING_BYTE_RANGE_LOCK'
  381.  381         );
  382.  382         // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-7.1
  383.  383         // the order, in this case, matters quite a lot - see Net_SFTP::_parseAttributes() to understand why
  384.  384         $this->attributes = array(
  385.  385             0x00000001 => 'NET_SFTP_ATTR_SIZE',
  386.  386             0x00000002 => 'NET_SFTP_ATTR_UIDGID'// defined in SFTPv3, removed in SFTPv4+
  387.  387             0x00000004 => 'NET_SFTP_ATTR_PERMISSIONS',
  388.  388             0x00000008 => 'NET_SFTP_ATTR_ACCESSTIME',
  389.  389             // 0x80000000 will yield a floating point on 32-bit systems and converting floating points to integers
  390.  390             // yields inconsistent behavior depending on how php is compiled.  so we left shift -1 (which, in
  391.  391             // two's compliment, consists of all 1 bits) by 31.  on 64-bit systems this'll yield 0xFFFFFFFF80000000.
  392.  392             // that's not a problem, however, and 'anded' and a 32-bit number, as all the leading 1 bits are ignored.
  393.  393             (-<< 31) & 0xFFFFFFFF => 'NET_SFTP_ATTR_EXTENDED'
  394.  394         );
  395.  395         // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-6.3
  396.  396         // the flag definitions change somewhat in SFTPv5+.  if SFTPv5+ support is added to this library, maybe name
  397.  397         // the array for that $this->open5_flags and similarly alter the constant names.
  398.  398         $this->open_flags = array(
  399.  399             0x00000001 => 'NET_SFTP_OPEN_READ',
  400.  400             0x00000002 => 'NET_SFTP_OPEN_WRITE',
  401.  401             0x00000004 => 'NET_SFTP_OPEN_APPEND',
  402.  402             0x00000008 => 'NET_SFTP_OPEN_CREATE',
  403.  403             0x00000010 => 'NET_SFTP_OPEN_TRUNCATE',
  404.  404             0x00000020 => 'NET_SFTP_OPEN_EXCL'
  405.  405         );
  406.  406         // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2
  407.  407         // see Net_SFTP::_parseLongname() for an explanation
  408.  408         $this->file_types = array(
  409.  409             => 'NET_SFTP_TYPE_REGULAR',
  410.  410             => 'NET_SFTP_TYPE_DIRECTORY',
  411.  411             => 'NET_SFTP_TYPE_SYMLINK',
  412.  412             => 'NET_SFTP_TYPE_SPECIAL',
  413.  413             => 'NET_SFTP_TYPE_UNKNOWN',
  414.  414             // the followin types were first defined for use in SFTPv5+
  415.  415             // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
  416.  416             => 'NET_SFTP_TYPE_SOCKET',
  417.  417             => 'NET_SFTP_TYPE_CHAR_DEVICE',
  418.  418             => 'NET_SFTP_TYPE_BLOCK_DEVICE',
  419.  419             => 'NET_SFTP_TYPE_FIFO'
  420.  420         );
  421.  421         $this->_define_array(
  422.  422             $this->packet_types,
  423.  423             $this->status_codes,
  424.  424             $this->attributes,
  425.  425             $this->open_flags,
  426.  426             $this->file_types
  427.  427         );
  428.  428 
  429.  429         if (!defined('NET_SFTP_QUEUE_SIZE')) {
  430.  430             define('NET_SFTP_QUEUE_SIZE'32);
  431.  431         }
  432.  432     }
  433.  433 
  434.  434     /**
  435.  435      * PHP4 compatible Default Constructor.
  436.  436      *
  437.  437      * @see self::__construct()
  438.  438      * @param string $host
  439.  439      * @param int $port
  440.  440      * @param int $timeout
  441.  441      * @access public
  442.  442      */
  443.  443     function Net_SFTP($host$port 22$timeout 10)
  444.  444     {
  445.  445         $this->__construct($host$port$timeout);
  446.  446     }
  447.  447 
  448.  448     /**
  449.  449      * Login
  450.  450      *
  451.  451      * @param string $username
  452.  452      * @param string $password
  453.  453      * @return bool
  454.  454      * @access public
  455.  455      */
  456.  456     function login($username)
  457.  457     {
  458.  458         $args func_get_args();
  459.  459         if (!call_user_func_array(array(&$this'_login'), $args)) {
  460.  460             return false;
  461.  461         }
  462.  462 
  463.  463         $this->window_size_server_to_client[NET_SFTP_CHANNEL] = $this->window_size;
  464.  464 
  465.  465         $packet pack(
  466.  466             'CNa*N3',
  467.  467             NET_SSH2_MSG_CHANNEL_OPEN,
  468.  468             strlen('session'),
  469.  469             'session',
  470.  470             NET_SFTP_CHANNEL,
  471.  471             $this->window_size,
  472.  472             0x4000
  473.  473         );
  474.  474 
  475.  475         if (!$this->_send_binary_packet($packet)) {
  476.  476             return false;
  477.  477         }
  478.  478 
  479.  479         $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_OPEN;
  480.  480 
  481.  481         $response $this->_get_channel_packet(NET_SFTP_CHANNELtrue);
  482.  482         if ($response === false) {
  483.  483             return false;
  484.  484         }
  485.  485 
  486.  486         $packet pack(
  487.  487             'CNNa*CNa*',
  488.  488             NET_SSH2_MSG_CHANNEL_REQUEST,
  489.  489             $this->server_channels[NET_SFTP_CHANNEL],
  490.  490             strlen('subsystem'),
  491.  491             'subsystem',
  492.  492             1,
  493.  493             strlen('sftp'),
  494.  494             'sftp'
  495.  495         );
  496.  496         if (!$this->_send_binary_packet($packet)) {
  497.  497             return false;
  498.  498         }
  499.  499 
  500.  500         $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
  501.  501 
  502.  502         $response $this->_get_channel_packet(NET_SFTP_CHANNELtrue);
  503.  503         if ($response === false) {
  504.  504             // from PuTTY's psftp.exe
  505.  505             $command "test -x /usr/lib/sftp-server && exec /usr/lib/sftp-server\n" .
  506.  506                        "test -x /usr/local/lib/sftp-server && exec /usr/local/lib/sftp-server\n" .
  507.  507                        "exec sftp-server";
  508.  508             // we don't do $this->exec($command, false) because exec() operates on a different channel and plus the SSH_MSG_CHANNEL_OPEN that exec() does
  509.  509             // is redundant
  510.  510             $packet pack(
  511.  511                 'CNNa*CNa*',
  512.  512                 NET_SSH2_MSG_CHANNEL_REQUEST,
  513.  513                 $this->server_channels[NET_SFTP_CHANNEL],
  514.  514                 strlen('exec'),
  515.  515                 'exec',
  516.  516                 1,
  517.  517                 strlen($command),
  518.  518                 $command
  519.  519             );
  520.  520             if (!$this->_send_binary_packet($packet)) {
  521.  521                 return false;
  522.  522             }
  523.  523 
  524.  524             $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_REQUEST;
  525.  525 
  526.  526             $response $this->_get_channel_packet(NET_SFTP_CHANNELtrue);
  527.  527             if ($response === false) {
  528.  528                 return false;
  529.  529             }
  530.  530         }
  531.  531 
  532.  532         $this->channel_status[NET_SFTP_CHANNEL] = NET_SSH2_MSG_CHANNEL_DATA;
  533.  533 
  534.  534         if (!$this->_send_sftp_packet(NET_SFTP_INIT"\0\0\0\3")) {
  535.  535             return false;
  536.  536         }
  537.  537 
  538.  538         $response $this->_get_sftp_packet();
  539.  539         if ($this->packet_type != NET_SFTP_VERSION) {
  540.  540             user_error('Expected SSH_FXP_VERSION');
  541.  541             return false;
  542.  542         }
  543.  543 
  544.  544         if (strlen($response) < 4) {
  545.  545             return false;
  546.  546         }
  547.  547         extract(unpack('Nversion'$this->_string_shift($response4)));
  548.  548         $this->version $version;
  549.  549         while (!empty($response)) {
  550.  550             if (strlen($response) < 4) {
  551.  551                 return false;
  552.  552             }
  553.  553             extract(unpack('Nlength'$this->_string_shift($response4)));
  554.  554             $key $this->_string_shift($response$length);
  555.  555             if (strlen($response) < 4) {
  556.  556                 return false;
  557.  557             }
  558.  558             extract(unpack('Nlength'$this->_string_shift($response4)));
  559.  559             $value $this->_string_shift($response$length);
  560.  560             $this->extensions[$key] = $value;
  561.  561         }
  562.  562 
  563.  563         /*
  564.  564          SFTPv4+ defines a 'newline' extension.  SFTPv3 seems to have unofficial support for it via 'newline@vandyke.com',
  565.  565          however, I'm not sure what 'newline@vandyke.com' is supposed to do (the fact that it's unofficial means that it's
  566.  566          not in the official SFTPv3 specs) and 'newline@vandyke.com' / 'newline' are likely not drop-in substitutes for
  567.  567          one another due to the fact that 'newline' comes with a SSH_FXF_TEXT bitmask whereas it seems unlikely that
  568.  568          'newline@vandyke.com' would.
  569.  569         */
  570.  570         /*
  571.  571         if (isset($this->extensions['newline@vandyke.com'])) {
  572.  572             $this->extensions['newline'] = $this->extensions['newline@vandyke.com'];
  573.  573             unset($this->extensions['newline@vandyke.com']);
  574.  574         }
  575.  575         */
  576.  576 
  577.  577         $this->request_id 1;
  578.  578 
  579.  579         /*
  580.  580          A Note on SFTPv4/5/6 support:
  581.  581          <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.1> states the following:
  582.  582 
  583.  583          "If the client wishes to interoperate with servers that support noncontiguous version
  584.  584           numbers it SHOULD send '3'"
  585.  585 
  586.  586          Given that the server only sends its version number after the client has already done so, the above
  587.  587          seems to be suggesting that v3 should be the default version.  This makes sense given that v3 is the
  588.  588          most popular.
  589.  589 
  590.  590          <http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-5.5> states the following;
  591.  591 
  592.  592          "If the server did not send the "versions" extension, or the version-from-list was not included, the
  593.  593           server MAY send a status response describing the failure, but MUST then close the channel without
  594.  594           processing any further requests."
  595.  595 
  596.  596          So what do you do if you have a client whose initial SSH_FXP_INIT packet says it implements v3 and
  597.  597          a server whose initial SSH_FXP_VERSION reply says it implements v4 and only v4?  If it only implements
  598.  598          v4, the "versions" extension is likely not going to have been sent so version re-negotiation as discussed
  599.  599          in draft-ietf-secsh-filexfer-13 would be quite impossible.  As such, what Net_SFTP would do is close the
  600.  600          channel and reopen it with a new and updated SSH_FXP_INIT packet.
  601.  601         */
  602.  602         switch ($this->version) {
  603.  603             case 2:
  604.  604             case 3:
  605.  605                 break;
  606.  606             default:
  607.  607                 return false;
  608.  608         }
  609.  609 
  610.  610         $this->pwd $this->_realpath('.');
  611.  611 
  612.  612         $this->_update_stat_cache($this->pwd, array());
  613.  613 
  614.  614         return true;
  615.  615     }
  616.  616 
  617.  617     /**
  618.  618      * Disable the stat cache
  619.  619      *
  620.  620      * @access public
  621.  621      */
  622.  622     function disableStatCache()
  623.  623     {
  624.  624         $this->use_stat_cache false;
  625.  625     }
  626.  626 
  627.  627     /**
  628.  628      * Enable the stat cache
  629.  629      *
  630.  630      * @access public
  631.  631      */
  632.  632     function enableStatCache()
  633.  633     {
  634.  634         $this->use_stat_cache true;
  635.  635     }
  636.  636 
  637.  637     /**
  638.  638      * Clear the stat cache
  639.  639      *
  640.  640      * @access public
  641.  641      */
  642.  642     function clearStatCache()
  643.  643     {
  644.  644         $this->stat_cache = array();
  645.  645     }
  646.  646 
  647.  647     /**
  648.  648      * Enable path canonicalization
  649.  649      *
  650.  650      * @access public
  651.  651      */
  652.  652     function enablePathCanonicalization()
  653.  653     {
  654.  654         $this->canonicalize_paths true;
  655.  655     }
  656.  656 
  657.  657     /**
  658.  658      * Enable path canonicalization
  659.  659      *
  660.  660      * @access public
  661.  661      */
  662.  662     function disablePathCanonicalization()
  663.  663     {
  664.  664         $this->canonicalize_paths false;
  665.  665     }
  666.  666 
  667.  667     /**
  668.  668      * Returns the current directory name
  669.  669      *
  670.  670      * @return mixed
  671.  671      * @access public
  672.  672      */
  673.  673     function pwd()
  674.  674     {
  675.  675         return $this->pwd;
  676.  676     }
  677.  677 
  678.  678     /**
  679.  679      * Logs errors
  680.  680      *
  681.  681      * @param string $response
  682.  682      * @param int $status
  683.  683      * @access public
  684.  684      */
  685.  685     function _logError($response$status = -1)
  686.  686     {
  687.  687         if ($status == -1) {
  688.  688             if (strlen($response) < 4) {
  689.  689                 return;
  690.  690             }
  691.  691             extract(unpack('Nstatus'$this->_string_shift($response4)));
  692.  692         }
  693.  693 
  694.  694         $error $this->status_codes[$status];
  695.  695 
  696.  696         if ($this->version || strlen($response) < 4) {
  697.  697             extract(unpack('Nlength'$this->_string_shift($response4)));
  698.  698             $this->sftp_errors[] = $error ': ' $this->_string_shift($response$length);
  699.  699         } else {
  700.  700             $this->sftp_errors[] = $error;
  701.  701         }
  702.  702     }
  703.  703 
  704.  704     /**
  705.  705      * Returns canonicalized absolute pathname
  706.  706      *
  707.  707      * realpath() expands all symbolic links and resolves references to '/./', '/../' and extra '/' characters in the input
  708.  708      * path and returns the canonicalized absolute pathname.
  709.  709      *
  710.  710      * @param string $path
  711.  711      * @return mixed
  712.  712      * @access public
  713.  713      */
  714.  714     function realpath($path)
  715.  715     {
  716.  716         return $this->_realpath($path);
  717.  717     }
  718.  718 
  719.  719     /**
  720.  720      * Canonicalize the Server-Side Path Name
  721.  721      *
  722.  722      * SFTP doesn't provide a mechanism by which the current working directory can be changed, so we'll emulate it.  Returns
  723.  723      * the absolute (canonicalized) path.
  724.  724      *
  725.  725      * If canonicalize_paths has been disabled using disablePathCanonicalization(), $path is returned as-is.
  726.  726      *
  727.  727      * @see self::chdir()
  728.  728      * @see self::disablePathCanonicalization()
  729.  729      * @param string $path
  730.  730      * @return mixed
  731.  731      * @access private
  732.  732      */
  733.  733     function _realpath($path)
  734.  734     {
  735.  735         if (!$this->canonicalize_paths) {
  736.  736             return $path;
  737.  737         }
  738.  738 
  739.  739         if ($this->pwd === false) {
  740.  740             // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.9
  741.  741             if (!$this->_send_sftp_packet(NET_SFTP_REALPATHpack('Na*'strlen($path), $path))) {
  742.  742                 return false;
  743.  743             }
  744.  744 
  745.  745             $response $this->_get_sftp_packet();
  746.  746             switch ($this->packet_type) {
  747.  747                 case NET_SFTP_NAME:
  748.  748                     // although SSH_FXP_NAME is implemented differently in SFTPv3 than it is in SFTPv4+, the following
  749.  749                     // should work on all SFTP versions since the only part of the SSH_FXP_NAME packet the following looks
  750.  750                     // at is the first part and that part is defined the same in SFTP versions 3 through 6.
  751.  751                     $this->_string_shift($response4); // skip over the count - it should be 1, anyway
  752.  752                     if (strlen($response) < 4) {
  753.  753                         return false;
  754.  754                     }
  755.  755                     extract(unpack('Nlength'$this->_string_shift($response4)));
  756.  756                     return $this->_string_shift($response$length);
  757.  757                 case NET_SFTP_STATUS:
  758.  758                     $this->_logError($response);
  759.  759                     return false;
  760.  760                 default:
  761.  761                     user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
  762.  762                     return false;
  763.  763             }
  764.  764         }
  765.  765 
  766.  766         if ($path[0] != '/') {
  767.  767             $path $this->pwd '/' $path;
  768.  768         }
  769.  769 
  770.  770         $path explode('/'$path);
  771.  771         $new = array();
  772.  772         foreach ($path as $dir) {
  773.  773             if (!strlen($dir)) {
  774.  774                 continue;
  775.  775             }
  776.  776             switch ($dir) {
  777.  777                 case '..':
  778.  778                     array_pop($new);
  779.  779                 case '.':
  780.  780                     break;
  781.  781                 default:
  782.  782                     $new[] = $dir;
  783.  783             }
  784.  784         }
  785.  785 
  786.  786         return '/' implode('/'$new);
  787.  787     }
  788.  788 
  789.  789     /**
  790.  790      * Changes the current directory
  791.  791      *
  792.  792      * @param string $dir
  793.  793      * @return bool
  794.  794      * @access public
  795.  795      */
  796.  796     function chdir($dir)
  797.  797     {
  798.  798         if (!($this->bitmap NET_SSH2_MASK_LOGIN)) {
  799.  799             return false;
  800.  800         }
  801.  801 
  802.  802         // assume current dir if $dir is empty
  803.  803         if ($dir === '') {
  804.  804             $dir './';
  805.  805         // suffix a slash if needed
  806.  806         } elseif ($dir[strlen($dir) - 1] != '/') {
  807.  807             $dir.= '/';
  808.  808         }
  809.  809 
  810.  810         $dir $this->_realpath($dir);
  811.  811 
  812.  812         // confirm that $dir is, in fact, a valid directory
  813.  813         if ($this->use_stat_cache && is_array($this->_query_stat_cache($dir))) {
  814.  814             $this->pwd $dir;
  815.  815             return true;
  816.  816         }
  817.  817 
  818.  818         // we could do a stat on the alleged $dir to see if it's a directory but that doesn't tell us
  819.  819         // the currently logged in user has the appropriate permissions or not. maybe you could see if
  820.  820         // the file's uid / gid match the currently logged in user's uid / gid but how there's no easy
  821.  821         // way to get those with SFTP
  822.  822 
  823.  823         if (!$this->_send_sftp_packet(NET_SFTP_OPENDIRpack('Na*'strlen($dir), $dir))) {
  824.  824             return false;
  825.  825         }
  826.  826 
  827.  827         // see Net_SFTP::nlist() for a more thorough explanation of the following
  828.  828         $response $this->_get_sftp_packet();
  829.  829         switch ($this->packet_type) {
  830.  830             case NET_SFTP_HANDLE:
  831.  831                 $handle substr($response4);
  832.  832                 break;
  833.  833             case NET_SFTP_STATUS:
  834.  834                 $this->_logError($response);
  835.  835                 return false;
  836.  836             default:
  837.  837                 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
  838.  838                 return false;
  839.  839         }
  840.  840 
  841.  841         if (!$this->_close_handle($handle)) {
  842.  842             return false;
  843.  843         }
  844.  844 
  845.  845         $this->_update_stat_cache($dir, array());
  846.  846 
  847.  847         $this->pwd $dir;
  848.  848         return true;
  849.  849     }
  850.  850 
  851.  851     /**
  852.  852      * Returns a list of files in the given directory
  853.  853      *
  854.  854      * @param string $dir
  855.  855      * @param bool $recursive
  856.  856      * @return mixed
  857.  857      * @access public
  858.  858      */
  859.  859     function nlist($dir '.'$recursive false)
  860.  860     {
  861.  861         return $this->_nlist_helper($dir$recursive'');
  862.  862     }
  863.  863 
  864.  864     /**
  865.  865      * Helper method for nlist
  866.  866      *
  867.  867      * @param string $dir
  868.  868      * @param bool $recursive
  869.  869      * @param string $relativeDir
  870.  870      * @return mixed
  871.  871      * @access private
  872.  872      */
  873.  873     function _nlist_helper($dir$recursive$relativeDir)
  874.  874     {
  875.  875         $files $this->_list($dirfalse);
  876.  876 
  877.  877         if (!$recursive || $files === false) {
  878.  878             return $files;
  879.  879         }
  880.  880 
  881.  881         $result = array();
  882.  882         foreach ($files as $value) {
  883.  883             if ($value == '.' || $value == '..') {
  884.  884                 if ($relativeDir == '') {
  885.  885                     $result[] = $value;
  886.  886                 }
  887.  887                 continue;
  888.  888             }
  889.  889             if (is_array($this->_query_stat_cache($this->_realpath($dir '/' $value)))) {
  890.  890                 $temp $this->_nlist_helper($dir '/' $valuetrue$relativeDir $value '/');
  891.  891                 $result array_merge($result$temp);
  892.  892             } else {
  893.  893                 $result[] = $relativeDir $value;
  894.  894             }
  895.  895         }
  896.  896 
  897.  897         return $result;
  898.  898     }
  899.  899 
  900.  900     /**
  901.  901      * Returns a detailed list of files in the given directory
  902.  902      *
  903.  903      * @param string $dir
  904.  904      * @param bool $recursive
  905.  905      * @return mixed
  906.  906      * @access public
  907.  907      */
  908.  908     function rawlist($dir '.'$recursive false)
  909.  909     {
  910.  910         $files $this->_list($dirtrue);
  911.  911         if (!$recursive || $files === false) {
  912.  912             return $files;
  913.  913         }
  914.  914 
  915.  915         static $depth 0;
  916.  916 
  917.  917         foreach ($files as $key => $value) {
  918.  918             if ($depth != && $key == '..') {
  919.  919                 unset($files[$key]);
  920.  920                 continue;
  921.  921             }
  922.  922             if ($key != '.' && $key != '..' && is_array($this->_query_stat_cache($this->_realpath($dir '/' $key)))) {
  923.  923                 $depth++;
  924.  924                 $files[$key] = $this->rawlist($dir '/' $keytrue);
  925.  925                 $depth--;
  926.  926             } else {
  927.  927                 $files[$key] = (object) $value;
  928.  928             }
  929.  929         }
  930.  930 
  931.  931         return $files;
  932.  932     }
  933.  933 
  934.  934     /**
  935.  935      * Reads a list, be it detailed or not, of files in the given directory
  936.  936      *
  937.  937      * @param string $dir
  938.  938      * @param bool $raw
  939.  939      * @return mixed
  940.  940      * @access private
  941.  941      */
  942.  942     function _list($dir$raw true)
  943.  943     {
  944.  944         if (!($this->bitmap NET_SSH2_MASK_LOGIN)) {
  945.  945             return false;
  946.  946         }
  947.  947 
  948.  948         $dir $this->_realpath($dir '/');
  949.  949         if ($dir === false) {
  950.  950             return false;
  951.  951         }
  952.  952 
  953.  953         // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.2
  954.  954         if (!$this->_send_sftp_packet(NET_SFTP_OPENDIRpack('Na*'strlen($dir), $dir))) {
  955.  955             return false;
  956.  956         }
  957.  957 
  958.  958         $response $this->_get_sftp_packet();
  959.  959         switch ($this->packet_type) {
  960.  960             case NET_SFTP_HANDLE:
  961.  961                 // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.2
  962.  962                 // since 'handle' is the last field in the SSH_FXP_HANDLE packet, we'll just remove the first four bytes that
  963.  963                 // represent the length of the string and leave it at that
  964.  964                 $handle substr($response4);
  965.  965                 break;
  966.  966             case NET_SFTP_STATUS:
  967.  967                 // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
  968.  968                 $this->_logError($response);
  969.  969                 return false;
  970.  970             default:
  971.  971                 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
  972.  972                 return false;
  973.  973         }
  974.  974 
  975.  975         $this->_update_stat_cache($dir, array());
  976.  976 
  977.  977         $contents = array();
  978.  978         while (true) {
  979.  979             // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.2
  980.  980             // why multiple SSH_FXP_READDIR packets would be sent when the response to a single one can span arbitrarily many
  981.  981             // SSH_MSG_CHANNEL_DATA messages is not known to me.
  982.  982             if (!$this->_send_sftp_packet(NET_SFTP_READDIRpack('Na*'strlen($handle), $handle))) {
  983.  983                 return false;
  984.  984             }
  985.  985 
  986.  986             $response $this->_get_sftp_packet();
  987.  987             switch ($this->packet_type) {
  988.  988                 case NET_SFTP_NAME:
  989.  989                     if (strlen($response) < 4) {
  990.  990                         return false;
  991.  991                     }
  992.  992                     extract(unpack('Ncount'$this->_string_shift($response4)));
  993.  993                     for ($i 0$i $count$i++) {
  994.  994                         if (strlen($response) < 4) {
  995.  995                             return false;
  996.  996                         }
  997.  997                         extract(unpack('Nlength'$this->_string_shift($response4)));
  998.  998                         $shortname $this->_string_shift($response$length);
  999.  999                         if (strlen($response) < 4) {
  1000. 1000                             return false;
  1001. 1001                         }
  1002. 1002                         extract(unpack('Nlength'$this->_string_shift($response4)));
  1003. 1003                         $longname $this->_string_shift($response$length);
  1004. 1004                         $attributes $this->_parseAttributes($response);
  1005. 1005                         if (!isset($attributes['type'])) {
  1006. 1006                             $fileType $this->_parseLongname($longname);
  1007. 1007                             if ($fileType) {
  1008. 1008                                 $attributes['type'] = $fileType;
  1009. 1009                             }
  1010. 1010                         }
  1011. 1011                         $contents[$shortname] = $attributes + array('filename' => $shortname);
  1012. 1012 
  1013. 1013                         if (isset($attributes['type']) && $attributes['type'] == NET_SFTP_TYPE_DIRECTORY && ($shortname != '.' && $shortname != '..')) {
  1014. 1014                             $this->_update_stat_cache($dir '/' $shortname, array());
  1015. 1015                         } else {
  1016. 1016                             if ($shortname == '..') {
  1017. 1017                                 $temp $this->_realpath($dir '/..') . '/.';
  1018. 1018                             } else {
  1019. 1019                                 $temp $dir '/' $shortname;
  1020. 1020                             }
  1021. 1021                             $this->_update_stat_cache($temp, (object) array('lstat' => $attributes));
  1022. 1022                         }
  1023. 1023                         // SFTPv6 has an optional boolean end-of-list field, but we'll ignore that, since the
  1024. 1024                         // final SSH_FXP_STATUS packet should tell us that, already.
  1025. 1025                     }
  1026. 1026                     break;
  1027. 1027                 case NET_SFTP_STATUS:
  1028. 1028                     if (strlen($response) < 4) {
  1029. 1029                         return false;
  1030. 1030                     }
  1031. 1031                     extract(unpack('Nstatus'$this->_string_shift($response4)));
  1032. 1032                     if ($status != NET_SFTP_STATUS_EOF) {
  1033. 1033                         $this->_logError($response$status);
  1034. 1034                         return false;
  1035. 1035                     }
  1036. 1036                     break 2;
  1037. 1037                 default:
  1038. 1038                     user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
  1039. 1039                     return false;
  1040. 1040             }
  1041. 1041         }
  1042. 1042 
  1043. 1043         if (!$this->_close_handle($handle)) {
  1044. 1044             return false;
  1045. 1045         }
  1046. 1046 
  1047. 1047         if (count($this->sortOptions)) {
  1048. 1048             uasort($contents, array(&$this'_comparator'));
  1049. 1049         }
  1050. 1050 
  1051. 1051         return $raw $contents array_keys($contents);
  1052. 1052     }
  1053. 1053 
  1054. 1054     /**
  1055. 1055      * Compares two rawlist entries using parameters set by setListOrder()
  1056. 1056      *
  1057. 1057      * Intended for use with uasort()
  1058. 1058      *
  1059. 1059      * @param array $a
  1060. 1060      * @param array $b
  1061. 1061      * @return int
  1062. 1062      * @access private
  1063. 1063      */
  1064. 1064     function _comparator($a$b)
  1065. 1065     {
  1066. 1066         switch (true) {
  1067. 1067             case $a['filename'] === '.' || $b['filename'] === '.':
  1068. 1068                 if ($a['filename'] === $b['filename']) {
  1069. 1069                     return 0;
  1070. 1070                 }
  1071. 1071                 return $a['filename'] === '.' ? -1;
  1072. 1072             case $a['filename'] === '..' || $b['filename'] === '..':
  1073. 1073                 if ($a['filename'] === $b['filename']) {
  1074. 1074                     return 0;
  1075. 1075                 }
  1076. 1076                 return $a['filename'] === '..' ? -1;
  1077. 1077             case isset($a['type']) && $a['type'] === NET_SFTP_TYPE_DIRECTORY:
  1078. 1078                 if (!isset($b['type'])) {
  1079. 1079                     return 1;
  1080. 1080                 }
  1081. 1081                 if ($b['type'] !== $a['type']) {
  1082. 1082                     return -1;
  1083. 1083                 }
  1084. 1084                 break;
  1085. 1085             case isset($b['type']) && $b['type'] === NET_SFTP_TYPE_DIRECTORY:
  1086. 1086                 return 1;
  1087. 1087         }
  1088. 1088         foreach ($this->sortOptions as $sort => $order) {
  1089. 1089             if (!isset($a[$sort]) || !isset($b[$sort])) {
  1090. 1090                 if (isset($a[$sort])) {
  1091. 1091                     return -1;
  1092. 1092                 }
  1093. 1093                 if (isset($b[$sort])) {
  1094. 1094                     return 1;
  1095. 1095                 }
  1096. 1096                 return 0;
  1097. 1097             }
  1098. 1098             switch ($sort) {
  1099. 1099                 case 'filename':
  1100. 1100                     $result strcasecmp($a['filename'], $b['filename']);
  1101. 1101                     if ($result) {
  1102. 1102                         return $order === SORT_DESC ? -$result $result;
  1103. 1103                     }
  1104. 1104                     break;
  1105. 1105                 case 'permissions':
  1106. 1106                 case 'mode':
  1107. 1107                     $a[$sort]&= 07777;
  1108. 1108                     $b[$sort]&= 07777;
  1109. 1109                 default:
  1110. 1110                     if ($a[$sort] === $b[$sort]) {
  1111. 1111                         break;
  1112. 1112                     }
  1113. 1113                     return $order === SORT_ASC $a[$sort] - $b[$sort] : $b[$sort] - $a[$sort];
  1114. 1114             }
  1115. 1115         }
  1116. 1116     }
  1117. 1117 
  1118. 1118     /**
  1119. 1119      * Defines how nlist() and rawlist() will be sorted - if at all.
  1120. 1120      *
  1121. 1121      * If sorting is enabled directories and files will be sorted independently with
  1122. 1122      * directories appearing before files in the resultant array that is returned.
  1123. 1123      *
  1124. 1124      * Any parameter returned by stat is a valid sort parameter for this function.
  1125. 1125      * Filename comparisons are case insensitive.
  1126. 1126      *
  1127. 1127      * Examples:
  1128. 1128      *
  1129. 1129      * $sftp->setListOrder('filename', SORT_ASC);
  1130. 1130      * $sftp->setListOrder('size', SORT_DESC, 'filename', SORT_ASC);
  1131. 1131      * $sftp->setListOrder(true);
  1132. 1132      *    Separates directories from files but doesn't do any sorting beyond that
  1133. 1133      * $sftp->setListOrder();
  1134. 1134      *    Don't do any sort of sorting
  1135. 1135      *
  1136. 1136      * @access public
  1137. 1137      */
  1138. 1138     function setListOrder()
  1139. 1139     {
  1140. 1140         $this->sortOptions = array();
  1141. 1141         $args func_get_args();
  1142. 1142         if (empty($args)) {
  1143. 1143             return;
  1144. 1144         }
  1145. 1145         $len count($args) & 0x7FFFFFFE;
  1146. 1146         for ($i 0$i $len$i+=2) {
  1147. 1147             $this->sortOptions[$args[$i]] = $args[$i 1];
  1148. 1148         }
  1149. 1149         if (!count($this->sortOptions)) {
  1150. 1150             $this->sortOptions = array('bogus' => true);
  1151. 1151         }
  1152. 1152     }
  1153. 1153 
  1154. 1154     /**
  1155. 1155      * Returns the file size, in bytes, or false, on failure
  1156. 1156      *
  1157. 1157      * Files larger than 4GB will show up as being exactly 4GB.
  1158. 1158      *
  1159. 1159      * @param string $filename
  1160. 1160      * @return mixed
  1161. 1161      * @access public
  1162. 1162      */
  1163. 1163     function size($filename)
  1164. 1164     {
  1165. 1165         if (!($this->bitmap NET_SSH2_MASK_LOGIN)) {
  1166. 1166             return false;
  1167. 1167         }
  1168. 1168 
  1169. 1169         $result $this->stat($filename);
  1170. 1170         if ($result === false) {
  1171. 1171             return false;
  1172. 1172         }
  1173. 1173         return isset($result['size']) ? $result['size'] : -1;
  1174. 1174     }
  1175. 1175 
  1176. 1176     /**
  1177. 1177      * Save files / directories to cache
  1178. 1178      *
  1179. 1179      * @param string $path
  1180. 1180      * @param mixed $value
  1181. 1181      * @access private
  1182. 1182      */
  1183. 1183     function _update_stat_cache($path$value)
  1184. 1184     {
  1185. 1185         if ($this->use_stat_cache === false) {
  1186. 1186             return;
  1187. 1187         }
  1188. 1188 
  1189. 1189         // preg_replace('#^/|/(?=/)|/$#', '', $dir) == str_replace('//', '/', trim($path, '/'))
  1190. 1190         $dirs explode('/'preg_replace('#^/|/(?=/)|/$#'''$path));
  1191. 1191 
  1192. 1192         $temp = &$this->stat_cache;
  1193. 1193         $max count($dirs) - 1;
  1194. 1194         foreach ($dirs as $i => $dir) {
  1195. 1195             // if $temp is an object that means one of two things.
  1196. 1196             //  1. a file was deleted and changed to a directory behind phpseclib's back
  1197. 1197             //  2. it's a symlink. when lstat is done it's unclear what it's a symlink to
  1198. 1198             if (is_object($temp)) {
  1199. 1199                 $temp = array();
  1200. 1200             }
  1201. 1201             if (!isset($temp[$dir])) {
  1202. 1202                 $temp[$dir] = array();
  1203. 1203             }
  1204. 1204             if ($i === $max) {
  1205. 1205                 if (is_object($temp[$dir]) && is_object($value)) {
  1206. 1206                     if (!isset($value->stat) && isset($temp[$dir]->stat)) {
  1207. 1207                         $value->stat $temp[$dir]->stat;
  1208. 1208                     }
  1209. 1209                     if (!isset($value->lstat) && isset($temp[$dir]->lstat)) {
  1210. 1210                         $value->lstat $temp[$dir]->lstat;
  1211. 1211                     }
  1212. 1212                 }
  1213. 1213                 $temp[$dir] = $value;
  1214. 1214                 break;
  1215. 1215             }
  1216. 1216             $temp = &$temp[$dir];
  1217. 1217         }
  1218. 1218     }
  1219. 1219 
  1220. 1220     /**
  1221. 1221      * Remove files / directories from cache
  1222. 1222      *
  1223. 1223      * @param string $path
  1224. 1224      * @return bool
  1225. 1225      * @access private
  1226. 1226      */
  1227. 1227     function _remove_from_stat_cache($path)
  1228. 1228     {
  1229. 1229         $dirs explode('/'preg_replace('#^/|/(?=/)|/$#'''$path));
  1230. 1230 
  1231. 1231         $temp = &$this->stat_cache;
  1232. 1232         $max count($dirs) - 1;
  1233. 1233         foreach ($dirs as $i => $dir) {
  1234. 1234             if ($i === $max) {
  1235. 1235                 unset($temp[$dir]);
  1236. 1236                 return true;
  1237. 1237             }
  1238. 1238             if (!isset($temp[$dir])) {
  1239. 1239                 return false;
  1240. 1240             }
  1241. 1241             $temp = &$temp[$dir];
  1242. 1242         }
  1243. 1243     }
  1244. 1244 
  1245. 1245     /**
  1246. 1246      * Checks cache for path
  1247. 1247      *
  1248. 1248      * Mainly used by file_exists
  1249. 1249      *
  1250. 1250      * @param string $dir
  1251. 1251      * @return mixed
  1252. 1252      * @access private
  1253. 1253      */
  1254. 1254     function _query_stat_cache($path)
  1255. 1255     {
  1256. 1256         $dirs explode('/'preg_replace('#^/|/(?=/)|/$#'''$path));
  1257. 1257 
  1258. 1258         $temp = &$this->stat_cache;
  1259. 1259         foreach ($dirs as $dir) {
  1260. 1260             if (!isset($temp[$dir])) {
  1261. 1261                 return null;
  1262. 1262             }
  1263. 1263             $temp = &$temp[$dir];
  1264. 1264         }
  1265. 1265         return $temp;
  1266. 1266     }
  1267. 1267 
  1268. 1268     /**
  1269. 1269      * Returns general information about a file.
  1270. 1270      *
  1271. 1271      * Returns an array on success and false otherwise.
  1272. 1272      *
  1273. 1273      * @param string $filename
  1274. 1274      * @return mixed
  1275. 1275      * @access public
  1276. 1276      */
  1277. 1277     function stat($filename)
  1278. 1278     {
  1279. 1279         if (!($this->bitmap NET_SSH2_MASK_LOGIN)) {
  1280. 1280             return false;
  1281. 1281         }
  1282. 1282 
  1283. 1283         $filename $this->_realpath($filename);
  1284. 1284         if ($filename === false) {
  1285. 1285             return false;
  1286. 1286         }
  1287. 1287 
  1288. 1288         if ($this->use_stat_cache) {
  1289. 1289             $result $this->_query_stat_cache($filename);
  1290. 1290             if (is_array($result) && isset($result['.']) && isset($result['.']->stat)) {
  1291. 1291                 return $result['.']->stat;
  1292. 1292             }
  1293. 1293             if (is_object($result) && isset($result->stat)) {
  1294. 1294                 return $result->stat;
  1295. 1295             }
  1296. 1296         }
  1297. 1297 
  1298. 1298         $stat $this->_stat($filenameNET_SFTP_STAT);
  1299. 1299         if ($stat === false) {
  1300. 1300             $this->_remove_from_stat_cache($filename);
  1301. 1301             return false;
  1302. 1302         }
  1303. 1303         if (isset($stat['type'])) {
  1304. 1304             if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
  1305. 1305                 $filename.= '/.';
  1306. 1306             }
  1307. 1307             $this->_update_stat_cache($filename, (object) array('stat' => $stat));
  1308. 1308             return $stat;
  1309. 1309         }
  1310. 1310 
  1311. 1311         $pwd $this->pwd;
  1312. 1312         $stat['type'] = $this->chdir($filename) ?
  1313. 1313             NET_SFTP_TYPE_DIRECTORY :
  1314. 1314             NET_SFTP_TYPE_REGULAR;
  1315. 1315         $this->pwd $pwd;
  1316. 1316 
  1317. 1317         if ($stat['type'] == NET_SFTP_TYPE_DIRECTORY) {
  1318. 1318             $filename.= '/.';
  1319. 1319         }
  1320. 1320         $this->_update_stat_cache($filename, (object) array('stat' => $stat));
  1321. 1321 
  1322. 1322         return $stat;
  1323. 1323     }
  1324. 1324 
  1325. 1325     /**
  1326. 1326      * Returns general information about a file or symbolic link.
  1327. 1327      *
  1328. 1328      * Returns an array on success and false otherwise.
  1329. 1329      *
  1330. 1330      * @param string $filename
  1331. 1331      * @return mixed
  1332. 1332      * @access public
  1333. 1333      */
  1334. 1334     function lstat($filename)
  1335. 1335     {
  1336. 1336         if (!($this->bitmap NET_SSH2_MASK_LOGIN)) {
  1337. 1337             return false;
  1338. 1338         }
  1339. 1339 
  1340. 1340         $filename $this->_realpath($filename);
  1341. 1341         if ($filename === false) {
  1342. 1342             return false;
  1343. 1343         }
  1344. 1344 
  1345. 1345         if ($this->use_stat_cache) {
  1346. 1346             $result $this->_query_stat_cache($filename);
  1347. 1347             if (is_array($result) && isset($result['.']) && isset($result['.']->lstat)) {
  1348. 1348                 return $result['.']->lstat;
  1349. 1349             }
  1350. 1350             if (is_object($result) && isset($result->lstat)) {
  1351. 1351                 return $result->lstat;
  1352. 1352             }
  1353. 1353         }
  1354. 1354 
  1355. 1355         $lstat $this->_stat($filenameNET_SFTP_LSTAT);
  1356. 1356         if ($lstat === false) {
  1357. 1357             $this->_remove_from_stat_cache($filename);
  1358. 1358             return false;
  1359. 1359         }
  1360. 1360         if (isset($lstat['type'])) {
  1361. 1361             if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) {
  1362. 1362                 $filename.= '/.';
  1363. 1363             }
  1364. 1364             $this->_update_stat_cache($filename, (object) array('lstat' => $lstat));
  1365. 1365             return $lstat;
  1366. 1366         }
  1367. 1367 
  1368. 1368         $stat $this->_stat($filenameNET_SFTP_STAT);
  1369. 1369 
  1370. 1370         if ($lstat != $stat) {
  1371. 1371             $lstat array_merge($lstat, array('type' => NET_SFTP_TYPE_SYMLINK));
  1372. 1372             $this->_update_stat_cache($filename, (object) array('lstat' => $lstat));
  1373. 1373             return $stat;
  1374. 1374         }
  1375. 1375 
  1376. 1376         $pwd $this->pwd;
  1377. 1377         $lstat['type'] = $this->chdir($filename) ?
  1378. 1378             NET_SFTP_TYPE_DIRECTORY :
  1379. 1379             NET_SFTP_TYPE_REGULAR;
  1380. 1380         $this->pwd $pwd;
  1381. 1381 
  1382. 1382         if ($lstat['type'] == NET_SFTP_TYPE_DIRECTORY) {
  1383. 1383             $filename.= '/.';
  1384. 1384         }
  1385. 1385         $this->_update_stat_cache($filename, (object) array('lstat' => $lstat));
  1386. 1386 
  1387. 1387         return $lstat;
  1388. 1388     }
  1389. 1389 
  1390. 1390     /**
  1391. 1391      * Returns general information about a file or symbolic link
  1392. 1392      *
  1393. 1393      * Determines information without calling Net_SFTP::realpath().
  1394. 1394      * The second parameter can be either NET_SFTP_STAT or NET_SFTP_LSTAT.
  1395. 1395      *
  1396. 1396      * @param string $filename
  1397. 1397      * @param int $type
  1398. 1398      * @return mixed
  1399. 1399      * @access private
  1400. 1400      */
  1401. 1401     function _stat($filename$type)
  1402. 1402     {
  1403. 1403         // SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
  1404. 1404         $packet pack('Na*'strlen($filename), $filename);
  1405. 1405         if (!$this->_send_sftp_packet($type$packet)) {
  1406. 1406             return false;
  1407. 1407         }
  1408. 1408 
  1409. 1409         $response $this->_get_sftp_packet();
  1410. 1410         switch ($this->packet_type) {
  1411. 1411             case NET_SFTP_ATTRS:
  1412. 1412                 return $this->_parseAttributes($response);
  1413. 1413             case NET_SFTP_STATUS:
  1414. 1414                 $this->_logError($response);
  1415. 1415                 return false;
  1416. 1416         }
  1417. 1417 
  1418. 1418         user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS');
  1419. 1419         return false;
  1420. 1420     }
  1421. 1421 
  1422. 1422     /**
  1423. 1423      * Truncates a file to a given length
  1424. 1424      *
  1425. 1425      * @param string $filename
  1426. 1426      * @param int $new_size
  1427. 1427      * @return bool
  1428. 1428      * @access public
  1429. 1429      */
  1430. 1430     function truncate($filename$new_size)
  1431. 1431     {
  1432. 1432         $attr pack('N3'NET_SFTP_ATTR_SIZE$new_size 4294967296$new_size); // 4294967296 == 0x100000000 == 1<<32
  1433. 1433 
  1434. 1434         return $this->_setstat($filename$attrfalse);
  1435. 1435     }
  1436. 1436 
  1437. 1437     /**
  1438. 1438      * Sets access and modification time of file.
  1439. 1439      *
  1440. 1440      * If the file does not exist, it will be created.
  1441. 1441      *
  1442. 1442      * @param string $filename
  1443. 1443      * @param int $time
  1444. 1444      * @param int $atime
  1445. 1445      * @return bool
  1446. 1446      * @access public
  1447. 1447      */
  1448. 1448     function touch($filename$time null$atime null)
  1449. 1449     {
  1450. 1450         if (!($this->bitmap NET_SSH2_MASK_LOGIN)) {
  1451. 1451             return false;
  1452. 1452         }
  1453. 1453 
  1454. 1454         $filename $this->_realpath($filename);
  1455. 1455         if ($filename === false) {
  1456. 1456             return false;
  1457. 1457         }
  1458. 1458 
  1459. 1459         if (!isset($time)) {
  1460. 1460             $time time();
  1461. 1461         }
  1462. 1462         if (!isset($atime)) {
  1463. 1463             $atime $time;
  1464. 1464         }
  1465. 1465 
  1466. 1466         $flags NET_SFTP_OPEN_WRITE NET_SFTP_OPEN_CREATE NET_SFTP_OPEN_EXCL;
  1467. 1467         $attr pack('N3'NET_SFTP_ATTR_ACCESSTIME$time$atime);
  1468. 1468         $packet pack('Na*Na*'strlen($filename), $filename$flags$attr);
  1469. 1469         if (!$this->_send_sftp_packet(NET_SFTP_OPEN$packet)) {
  1470. 1470             return false;
  1471. 1471         }
  1472. 1472 
  1473. 1473         $response $this->_get_sftp_packet();
  1474. 1474         switch ($this->packet_type) {
  1475. 1475             case NET_SFTP_HANDLE:
  1476. 1476                 return $this->_close_handle(substr($response4));
  1477. 1477             case NET_SFTP_STATUS:
  1478. 1478                 $this->_logError($response);
  1479. 1479                 break;
  1480. 1480             default:
  1481. 1481                 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
  1482. 1482                 return false;
  1483. 1483         }
  1484. 1484 
  1485. 1485         return $this->_setstat($filename$attrfalse);
  1486. 1486     }
  1487. 1487 
  1488. 1488     /**
  1489. 1489      * Changes file or directory owner
  1490. 1490      *
  1491. 1491      * Returns true on success or false on error.
  1492. 1492      *
  1493. 1493      * @param string $filename
  1494. 1494      * @param int $uid
  1495. 1495      * @param bool $recursive
  1496. 1496      * @return bool
  1497. 1497      * @access public
  1498. 1498      */
  1499. 1499     function chown($filename$uid$recursive false)
  1500. 1500     {
  1501. 1501         // quoting from <http://www.kernel.org/doc/man-pages/online/pages/man2/chown.2.html>,
  1502. 1502         // "if the owner or group is specified as -1, then that ID is not changed"
  1503. 1503         $attr pack('N3'NET_SFTP_ATTR_UIDGID$uid, -1);
  1504. 1504 
  1505. 1505         return $this->_setstat($filename$attr$recursive);
  1506. 1506     }
  1507. 1507 
  1508. 1508     /**
  1509. 1509      * Changes file or directory group
  1510. 1510      *
  1511. 1511      * Returns true on success or false on error.
  1512. 1512      *
  1513. 1513      * @param string $filename
  1514. 1514      * @param int $gid
  1515. 1515      * @param bool $recursive
  1516. 1516      * @return bool
  1517. 1517      * @access public
  1518. 1518      */
  1519. 1519     function chgrp($filename$gid$recursive false)
  1520. 1520     {
  1521. 1521         $attr pack('N3'NET_SFTP_ATTR_UIDGID, -1$gid);
  1522. 1522 
  1523. 1523         return $this->_setstat($filename$attr$recursive);
  1524. 1524     }
  1525. 1525 
  1526. 1526     /**
  1527. 1527      * Set permissions on a file.
  1528. 1528      *
  1529. 1529      * Returns the new file permissions on success or false on error.
  1530. 1530      * If $recursive is true than this just returns true or false.
  1531. 1531      *
  1532. 1532      * @param int $mode
  1533. 1533      * @param string $filename
  1534. 1534      * @param bool $recursive
  1535. 1535      * @return mixed
  1536. 1536      * @access public
  1537. 1537      */
  1538. 1538     function chmod($mode$filename$recursive false)
  1539. 1539     {
  1540. 1540         if (is_string($mode) && is_int($filename)) {
  1541. 1541             $temp $mode;
  1542. 1542             $mode $filename;
  1543. 1543             $filename $temp;
  1544. 1544         }
  1545. 1545 
  1546. 1546         $attr pack('N2'NET_SFTP_ATTR_PERMISSIONS$mode 07777);
  1547. 1547         if (!$this->_setstat($filename$attr$recursive)) {
  1548. 1548             return false;
  1549. 1549         }
  1550. 1550         if ($recursive) {
  1551. 1551             return true;
  1552. 1552         }
  1553. 1553 
  1554. 1554         $filename $this->realpath($filename);
  1555. 1555         // rather than return what the permissions *should* be, we'll return what they actually are.  this will also
  1556. 1556         // tell us if the file actually exists.
  1557. 1557         // incidentally, SFTPv4+ adds an additional 32-bit integer field - flags - to the following:
  1558. 1558         $packet pack('Na*'strlen($filename), $filename);
  1559. 1559         if (!$this->_send_sftp_packet(NET_SFTP_STAT$packet)) {
  1560. 1560             return false;
  1561. 1561         }
  1562. 1562 
  1563. 1563         $response $this->_get_sftp_packet();
  1564. 1564         switch ($this->packet_type) {
  1565. 1565             case NET_SFTP_ATTRS:
  1566. 1566                 $attrs $this->_parseAttributes($response);
  1567. 1567                 return $attrs['permissions'];
  1568. 1568             case NET_SFTP_STATUS:
  1569. 1569                 $this->_logError($response);
  1570. 1570                 return false;
  1571. 1571         }
  1572. 1572 
  1573. 1573         user_error('Expected SSH_FXP_ATTRS or SSH_FXP_STATUS');
  1574. 1574         return false;
  1575. 1575     }
  1576. 1576 
  1577. 1577     /**
  1578. 1578      * Sets information about a file
  1579. 1579      *
  1580. 1580      * @param string $filename
  1581. 1581      * @param string $attr
  1582. 1582      * @param bool $recursive
  1583. 1583      * @return bool
  1584. 1584      * @access private
  1585. 1585      */
  1586. 1586     function _setstat($filename$attr$recursive)
  1587. 1587     {
  1588. 1588         if (!($this->bitmap NET_SSH2_MASK_LOGIN)) {
  1589. 1589             return false;
  1590. 1590         }
  1591. 1591 
  1592. 1592         $filename $this->_realpath($filename);
  1593. 1593         if ($filename === false) {
  1594. 1594             return false;
  1595. 1595         }
  1596. 1596 
  1597. 1597         $this->_remove_from_stat_cache($filename);
  1598. 1598 
  1599. 1599         if ($recursive) {
  1600. 1600             $i 0;
  1601. 1601             $result $this->_setstat_recursive($filename$attr$i);
  1602. 1602             $this->_read_put_responses($i);
  1603. 1603             return $result;
  1604. 1604         }
  1605. 1605 
  1606. 1606         // SFTPv4+ has an additional byte field - type - that would need to be sent, as well. setting it to
  1607. 1607         // SSH_FILEXFER_TYPE_UNKNOWN might work. if not, we'd have to do an SSH_FXP_STAT before doing an SSH_FXP_SETSTAT.
  1608. 1608         if (!$this->_send_sftp_packet(NET_SFTP_SETSTATpack('Na*a*'strlen($filename), $filename$attr))) {
  1609. 1609             return false;
  1610. 1610         }
  1611. 1611 
  1612. 1612         /*
  1613. 1613          "Because some systems must use separate system calls to set various attributes, it is possible that a failure
  1614. 1614           response will be returned, but yet some of the attributes may be have been successfully modified.  If possible,
  1615. 1615           servers SHOULD avoid this situation; however, clients MUST be aware that this is possible."
  1616. 1616 
  1617. 1617           -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.6
  1618. 1618         */
  1619. 1619         $response $this->_get_sftp_packet();
  1620. 1620         if ($this->packet_type != NET_SFTP_STATUS) {
  1621. 1621             user_error('Expected SSH_FXP_STATUS');
  1622. 1622             return false;
  1623. 1623         }
  1624. 1624 
  1625. 1625         if (strlen($response) < 4) {
  1626. 1626             return false;
  1627. 1627         }
  1628. 1628         extract(unpack('Nstatus'$this->_string_shift($response4)));
  1629. 1629         if ($status != NET_SFTP_STATUS_OK) {
  1630. 1630             $this->_logError($response$status);
  1631. 1631             return false;
  1632. 1632         }
  1633. 1633 
  1634. 1634         return true;
  1635. 1635     }
  1636. 1636 
  1637. 1637     /**
  1638. 1638      * Recursively sets information on directories on the SFTP server
  1639. 1639      *
  1640. 1640      * Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
  1641. 1641      *
  1642. 1642      * @param string $path
  1643. 1643      * @param string $attr
  1644. 1644      * @param int $i
  1645. 1645      * @return bool
  1646. 1646      * @access private
  1647. 1647      */
  1648. 1648     function _setstat_recursive($path$attr, &$i)
  1649. 1649     {
  1650. 1650         if (!$this->_read_put_responses($i)) {
  1651. 1651             return false;
  1652. 1652         }
  1653. 1653         $i 0;
  1654. 1654         $entries $this->_list($pathtrue);
  1655. 1655 
  1656. 1656         if ($entries === false) {
  1657. 1657             return $this->_setstat($path$attrfalse);
  1658. 1658         }
  1659. 1659 
  1660. 1660         // normally $entries would have at least . and .. but it might not if the directories
  1661. 1661         // permissions didn't allow reading
  1662. 1662         if (empty($entries)) {
  1663. 1663             return false;
  1664. 1664         }
  1665. 1665 
  1666. 1666         unset($entries['.'], $entries['..']);
  1667. 1667         foreach ($entries as $filename => $props) {
  1668. 1668             if (!isset($props['type'])) {
  1669. 1669                 return false;
  1670. 1670             }
  1671. 1671 
  1672. 1672             $temp $path '/' $filename;
  1673. 1673             if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
  1674. 1674                 if (!$this->_setstat_recursive($temp$attr$i)) {
  1675. 1675                     return false;
  1676. 1676                 }
  1677. 1677             } else {
  1678. 1678                 if (!$this->_send_sftp_packet(NET_SFTP_SETSTATpack('Na*a*'strlen($temp), $temp$attr))) {
  1679. 1679                     return false;
  1680. 1680                 }
  1681. 1681 
  1682. 1682                 $i++;
  1683. 1683 
  1684. 1684                 if ($i >= NET_SFTP_QUEUE_SIZE) {
  1685. 1685                     if (!$this->_read_put_responses($i)) {
  1686. 1686                         return false;
  1687. 1687                     }
  1688. 1688                     $i 0;
  1689. 1689                 }
  1690. 1690             }
  1691. 1691         }
  1692. 1692 
  1693. 1693         if (!$this->_send_sftp_packet(NET_SFTP_SETSTATpack('Na*a*'strlen($path), $path$attr))) {
  1694. 1694             return false;
  1695. 1695         }
  1696. 1696 
  1697. 1697         $i++;
  1698. 1698 
  1699. 1699         if ($i >= NET_SFTP_QUEUE_SIZE) {
  1700. 1700             if (!$this->_read_put_responses($i)) {
  1701. 1701                 return false;
  1702. 1702             }
  1703. 1703             $i 0;
  1704. 1704         }
  1705. 1705 
  1706. 1706         return true;
  1707. 1707     }
  1708. 1708 
  1709. 1709     /**
  1710. 1710      * Return the target of a symbolic link
  1711. 1711      *
  1712. 1712      * @param string $link
  1713. 1713      * @return mixed
  1714. 1714      * @access public
  1715. 1715      */
  1716. 1716     function readlink($link)
  1717. 1717     {
  1718. 1718         if (!($this->bitmap NET_SSH2_MASK_LOGIN)) {
  1719. 1719             return false;
  1720. 1720         }
  1721. 1721 
  1722. 1722         $link $this->_realpath($link);
  1723. 1723 
  1724. 1724         if (!$this->_send_sftp_packet(NET_SFTP_READLINKpack('Na*'strlen($link), $link))) {
  1725. 1725             return false;
  1726. 1726         }
  1727. 1727 
  1728. 1728         $response $this->_get_sftp_packet();
  1729. 1729         switch ($this->packet_type) {
  1730. 1730             case NET_SFTP_NAME:
  1731. 1731                 break;
  1732. 1732             case NET_SFTP_STATUS:
  1733. 1733                 $this->_logError($response);
  1734. 1734                 return false;
  1735. 1735             default:
  1736. 1736                 user_error('Expected SSH_FXP_NAME or SSH_FXP_STATUS');
  1737. 1737                 return false;
  1738. 1738         }
  1739. 1739 
  1740. 1740         if (strlen($response) < 4) {
  1741. 1741             return false;
  1742. 1742         }
  1743. 1743         extract(unpack('Ncount'$this->_string_shift($response4)));
  1744. 1744         // the file isn't a symlink
  1745. 1745         if (!$count) {
  1746. 1746             return false;
  1747. 1747         }
  1748. 1748 
  1749. 1749         if (strlen($response) < 4) {
  1750. 1750             return false;
  1751. 1751         }
  1752. 1752         extract(unpack('Nlength'$this->_string_shift($response4)));
  1753. 1753         return $this->_string_shift($response$length);
  1754. 1754     }
  1755. 1755 
  1756. 1756     /**
  1757. 1757      * Create a symlink
  1758. 1758      *
  1759. 1759      * symlink() creates a symbolic link to the existing target with the specified name link.
  1760. 1760      *
  1761. 1761      * @param string $target
  1762. 1762      * @param string $link
  1763. 1763      * @return bool
  1764. 1764      * @access public
  1765. 1765      */
  1766. 1766     function symlink($target$link)
  1767. 1767     {
  1768. 1768         if (!($this->bitmap NET_SSH2_MASK_LOGIN)) {
  1769. 1769             return false;
  1770. 1770         }
  1771. 1771 
  1772. 1772         //$target = $this->_realpath($target);
  1773. 1773         $link $this->_realpath($link);
  1774. 1774 
  1775. 1775         $packet pack('Na*Na*'strlen($target), $targetstrlen($link), $link);
  1776. 1776         if (!$this->_send_sftp_packet(NET_SFTP_SYMLINK$packet)) {
  1777. 1777             return false;
  1778. 1778         }
  1779. 1779 
  1780. 1780         $response $this->_get_sftp_packet();
  1781. 1781         if ($this->packet_type != NET_SFTP_STATUS) {
  1782. 1782             user_error('Expected SSH_FXP_STATUS');
  1783. 1783             return false;
  1784. 1784         }
  1785. 1785 
  1786. 1786         if (strlen($response) < 4) {
  1787. 1787             return false;
  1788. 1788         }
  1789. 1789         extract(unpack('Nstatus'$this->_string_shift($response4)));
  1790. 1790         if ($status != NET_SFTP_STATUS_OK) {
  1791. 1791             $this->_logError($response$status);
  1792. 1792             return false;
  1793. 1793         }
  1794. 1794 
  1795. 1795         return true;
  1796. 1796     }
  1797. 1797 
  1798. 1798     /**
  1799. 1799      * Creates a directory.
  1800. 1800      *
  1801. 1801      * @param string $dir
  1802. 1802      * @return bool
  1803. 1803      * @access public
  1804. 1804      */
  1805. 1805     function mkdir($dir$mode = -1$recursive false)
  1806. 1806     {
  1807. 1807         if (!($this->bitmap NET_SSH2_MASK_LOGIN)) {
  1808. 1808             return false;
  1809. 1809         }
  1810. 1810 
  1811. 1811         $dir $this->_realpath($dir);
  1812. 1812         // by not providing any permissions, hopefully the server will use the logged in users umask - their
  1813. 1813         // default permissions.
  1814. 1814         $attr $mode == -"\0\0\0\0" pack('N2'NET_SFTP_ATTR_PERMISSIONS$mode 07777);
  1815. 1815 
  1816. 1816         if ($recursive) {
  1817. 1817             $dirs explode('/'preg_replace('#/(?=/)|/$#'''$dir));
  1818. 1818             if (empty($dirs[0])) {
  1819. 1819                 array_shift($dirs);
  1820. 1820                 $dirs[0] = '/' $dirs[0];
  1821. 1821             }
  1822. 1822             for ($i 0$i count($dirs); $i++) {
  1823. 1823                 $temp array_slice($dirs0$i 1);
  1824. 1824                 $temp implode('/'$temp);
  1825. 1825                 $result $this->_mkdir_helper($temp$attr);
  1826. 1826             }
  1827. 1827             return $result;
  1828. 1828         }
  1829. 1829 
  1830. 1830         return $this->_mkdir_helper($dir$attr);
  1831. 1831     }
  1832. 1832 
  1833. 1833     /**
  1834. 1834      * Helper function for directory creation
  1835. 1835      *
  1836. 1836      * @param string $dir
  1837. 1837      * @return bool
  1838. 1838      * @access private
  1839. 1839      */
  1840. 1840     function _mkdir_helper($dir$attr)
  1841. 1841     {
  1842. 1842         if (!$this->_send_sftp_packet(NET_SFTP_MKDIRpack('Na*a*'strlen($dir), $dir$attr))) {
  1843. 1843             return false;
  1844. 1844         }
  1845. 1845 
  1846. 1846         $response $this->_get_sftp_packet();
  1847. 1847         if ($this->packet_type != NET_SFTP_STATUS) {
  1848. 1848             user_error('Expected SSH_FXP_STATUS');
  1849. 1849             return false;
  1850. 1850         }
  1851. 1851 
  1852. 1852         if (strlen($response) < 4) {
  1853. 1853             return false;
  1854. 1854         }
  1855. 1855         extract(unpack('Nstatus'$this->_string_shift($response4)));
  1856. 1856         if ($status != NET_SFTP_STATUS_OK) {
  1857. 1857             $this->_logError($response$status);
  1858. 1858             return false;
  1859. 1859         }
  1860. 1860 
  1861. 1861         return true;
  1862. 1862     }
  1863. 1863 
  1864. 1864     /**
  1865. 1865      * Removes a directory.
  1866. 1866      *
  1867. 1867      * @param string $dir
  1868. 1868      * @return bool
  1869. 1869      * @access public
  1870. 1870      */
  1871. 1871     function rmdir($dir)
  1872. 1872     {
  1873. 1873         if (!($this->bitmap NET_SSH2_MASK_LOGIN)) {
  1874. 1874             return false;
  1875. 1875         }
  1876. 1876 
  1877. 1877         $dir $this->_realpath($dir);
  1878. 1878         if ($dir === false) {
  1879. 1879             return false;
  1880. 1880         }
  1881. 1881 
  1882. 1882         if (!$this->_send_sftp_packet(NET_SFTP_RMDIRpack('Na*'strlen($dir), $dir))) {
  1883. 1883             return false;
  1884. 1884         }
  1885. 1885 
  1886. 1886         $response $this->_get_sftp_packet();
  1887. 1887         if ($this->packet_type != NET_SFTP_STATUS) {
  1888. 1888             user_error('Expected SSH_FXP_STATUS');
  1889. 1889             return false;
  1890. 1890         }
  1891. 1891 
  1892. 1892         if (strlen($response) < 4) {
  1893. 1893             return false;
  1894. 1894         }
  1895. 1895         extract(unpack('Nstatus'$this->_string_shift($response4)));
  1896. 1896         if ($status != NET_SFTP_STATUS_OK) {
  1897. 1897             // presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED?
  1898. 1898             $this->_logError($response$status);
  1899. 1899             return false;
  1900. 1900         }
  1901. 1901 
  1902. 1902         $this->_remove_from_stat_cache($dir);
  1903. 1903         // the following will do a soft delete, which would be useful if you deleted a file
  1904. 1904         // and then tried to do a stat on the deleted file. the above, in contrast, does
  1905. 1905         // a hard delete
  1906. 1906         //$this->_update_stat_cache($dir, false);
  1907. 1907 
  1908. 1908         return true;
  1909. 1909     }
  1910. 1910 
  1911. 1911     /**
  1912. 1912      * Uploads a file to the SFTP server.
  1913. 1913      *
  1914. 1914      * By default, Net_SFTP::put() does not read from the local filesystem.  $data is dumped directly into $remote_file.
  1915. 1915      * So, for example, if you set $data to 'filename.ext' and then do Net_SFTP::get(), you will get a file, twelve bytes
  1916. 1916      * long, containing 'filename.ext' as its contents.
  1917. 1917      *
  1918. 1918      * Setting $mode to NET_SFTP_LOCAL_FILE will change the above behavior.  With NET_SFTP_LOCAL_FILE, $remote_file will
  1919. 1919      * contain as many bytes as filename.ext does on your local filesystem.  If your filename.ext is 1MB then that is how
  1920. 1920      * large $remote_file will be, as well.
  1921. 1921      *
  1922. 1922      * If $data is a resource then it'll be used as a resource instead.
  1923. 1923      *
  1924. 1924      *
  1925. 1925      * Setting $mode to NET_SFTP_CALLBACK will use $data as callback function, which gets only one parameter -- number
  1926. 1926      * of bytes to return, and returns a string if there is some data or null if there is no more data
  1927. 1927      *
  1928. 1928      * Currently, only binary mode is supported.  As such, if the line endings need to be adjusted, you will need to take
  1929. 1929      * care of that, yourself.
  1930. 1930      *
  1931. 1931      * $mode can take an additional two parameters - NET_SFTP_RESUME and NET_SFTP_RESUME_START. These are bitwise AND'd with
  1932. 1932      * $mode. So if you want to resume upload of a 300mb file on the local file system you'd set $mode to the following:
  1933. 1933      *
  1934. 1934      * NET_SFTP_LOCAL_FILE | NET_SFTP_RESUME
  1935. 1935      *
  1936. 1936      * If you wanted to simply append the full contents of a local file to the full contents of a remote file you'd replace
  1937. 1937      * NET_SFTP_RESUME with NET_SFTP_RESUME_START.
  1938. 1938      *
  1939. 1939      * If $mode & (NET_SFTP_RESUME | NET_SFTP_RESUME_START) then NET_SFTP_RESUME_START will be assumed.
  1940. 1940      *
  1941. 1941      * $start and $local_start give you more fine grained control over this process and take precident over NET_SFTP_RESUME
  1942. 1942      * when they're non-negative. ie. $start could let you write at the end of a file (like NET_SFTP_RESUME) or in the middle
  1943. 1943      * of one. $local_start could let you start your reading from the end of a file (like NET_SFTP_RESUME_START) or in the
  1944. 1944      * middle of one.
  1945. 1945      *
  1946. 1946      * Setting $local_start to > 0 or $mode | NET_SFTP_RESUME_START doesn't do anything unless $mode | NET_SFTP_LOCAL_FILE.
  1947. 1947      *
  1948. 1948      * @param string $remote_file
  1949. 1949      * @param string|resource $data
  1950. 1950      * @param int $mode
  1951. 1951      * @param int $start
  1952. 1952      * @param int $local_start
  1953. 1953      * @param callable|null $progressCallback
  1954. 1954      * @return bool
  1955. 1955      * @access public
  1956. 1956      * @internal ASCII mode for SFTPv4/5/6 can be supported by adding a new function - Net_SFTP::setMode().
  1957. 1957      */
  1958. 1958     function put($remote_file$data$mode NET_SFTP_STRING$start = -1$local_start = -1$progressCallback null)
  1959. 1959     {
  1960. 1960         if (!($this->bitmap NET_SSH2_MASK_LOGIN)) {
  1961. 1961             return false;
  1962. 1962         }
  1963. 1963 
  1964. 1964         $remote_file $this->_realpath($remote_file);
  1965. 1965         if ($remote_file === false) {
  1966. 1966             return false;
  1967. 1967         }
  1968. 1968 
  1969. 1969         $this->_remove_from_stat_cache($remote_file);
  1970. 1970 
  1971. 1971         $flags NET_SFTP_OPEN_WRITE NET_SFTP_OPEN_CREATE;
  1972. 1972         // according to the SFTP specs, NET_SFTP_OPEN_APPEND should "force all writes to append data at the end of the file."
  1973. 1973         // in practice, it doesn't seem to do that.
  1974. 1974         //$flags|= ($mode & NET_SFTP_RESUME) ? NET_SFTP_OPEN_APPEND : NET_SFTP_OPEN_TRUNCATE;
  1975. 1975 
  1976. 1976         if ($start >= 0) {
  1977. 1977             $offset $start;
  1978. 1978         } elseif ($mode NET_SFTP_RESUME) {
  1979. 1979             // if NET_SFTP_OPEN_APPEND worked as it should _size() wouldn't need to be called
  1980. 1980             $size $this->size($remote_file);
  1981. 1981             $offset $size !== false $size 0;
  1982. 1982         } else {
  1983. 1983             $offset 0;
  1984. 1984             $flags|= NET_SFTP_OPEN_TRUNCATE;
  1985. 1985         }
  1986. 1986 
  1987. 1987         $packet pack('Na*N2'strlen($remote_file), $remote_file$flags0);
  1988. 1988         if (!$this->_send_sftp_packet(NET_SFTP_OPEN$packet)) {
  1989. 1989             return false;
  1990. 1990         }
  1991. 1991 
  1992. 1992         $response $this->_get_sftp_packet();
  1993. 1993         switch ($this->packet_type) {
  1994. 1994             case NET_SFTP_HANDLE:
  1995. 1995                 $handle substr($response4);
  1996. 1996                 break;
  1997. 1997             case NET_SFTP_STATUS:
  1998. 1998                 $this->_logError($response);
  1999. 1999                 return false;
  2000. 2000             default:
  2001. 2001                 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
  2002. 2002                 return false;
  2003. 2003         }
  2004. 2004 
  2005. 2005         // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.2.3
  2006. 2006         $dataCallback false;
  2007. 2007         switch (true) {
  2008. 2008             case $mode NET_SFTP_CALLBACK:
  2009. 2009                 if (!is_callable($data)) {
  2010. 2010                     user_error("\$data should be is_callable if you set NET_SFTP_CALLBACK flag");
  2011. 2011                 }
  2012. 2012                 $dataCallback $data;
  2013. 2013                 // do nothing
  2014. 2014                 break;
  2015. 2015             case is_resource($data):
  2016. 2016                 $mode $mode & ~NET_SFTP_LOCAL_FILE;
  2017. 2017                 $info stream_get_meta_data($data);
  2018. 2018                 if ($info['wrapper_type'] == 'PHP' && $info['stream_type'] == 'Input') {
  2019. 2019                     $fp fopen('php://memory''w+');
  2020. 2020                     stream_copy_to_stream($data$fp);
  2021. 2021                     rewind($fp);
  2022. 2022                 } else {
  2023. 2023                     $fp $data;
  2024. 2024                 }
  2025. 2025                 break;
  2026. 2026             case $mode NET_SFTP_LOCAL_FILE:
  2027. 2027                 if (!is_file($data)) {
  2028. 2028                     user_error("$data is not a valid file");
  2029. 2029                     return false;
  2030. 2030                 }
  2031. 2031                 $fp = @fopen($data'rb');
  2032. 2032                 if (!$fp) {
  2033. 2033                     return false;
  2034. 2034                 }
  2035. 2035         }
  2036. 2036 
  2037. 2037         if (isset($fp)) {
  2038. 2038             $stat fstat($fp);
  2039. 2039             $size = !empty($stat) ? $stat['size'] : 0;
  2040. 2040 
  2041. 2041             if ($local_start >= 0) {
  2042. 2042                 fseek($fp$local_start);
  2043. 2043                 $size-= $local_start;
  2044. 2044             }
  2045. 2045         } elseif ($dataCallback) {
  2046. 2046             $size 0;
  2047. 2047         } else {
  2048. 2048             $size strlen($data);
  2049. 2049         }
  2050. 2050 
  2051. 2051         $sent 0;
  2052. 2052         $size $size ? ($size 0x7FFFFFFF) + 0x80000000 $size;
  2053. 2053 
  2054. 2054         $sftp_packet_size 4096// PuTTY uses 4096
  2055. 2055         // make the SFTP packet be exactly 4096 bytes by including the bytes in the NET_SFTP_WRITE packets "header"
  2056. 2056         $sftp_packet_size-= strlen($handle) + 25;
  2057. 2057         $i 0;
  2058. 2058         while ($dataCallback || ($size === || $sent $size)) {
  2059. 2059             if ($dataCallback) {
  2060. 2060                 $temp call_user_func($dataCallback$sftp_packet_size);
  2061. 2061                 if (is_null($temp)) {
  2062. 2062                     break;
  2063. 2063                 }
  2064. 2064             } else {
  2065. 2065                 $temp = isset($fp) ? fread($fp$sftp_packet_size) : substr($data$sent$sftp_packet_size);
  2066. 2066                 if ($temp === false || $temp === '') {
  2067. 2067                     break;
  2068. 2068                 }
  2069. 2069             }
  2070. 2070 
  2071. 2071             $subtemp $offset $sent;
  2072. 2072             $packet pack('Na*N3a*'strlen($handle), $handle$subtemp 4294967296$subtempstrlen($temp), $temp);
  2073. 2073             if (!$this->_send_sftp_packet(NET_SFTP_WRITE$packet)) {
  2074. 2074                 if ($mode NET_SFTP_LOCAL_FILE) {
  2075. 2075                     fclose($fp);
  2076. 2076                 }
  2077. 2077                 return false;
  2078. 2078             }
  2079. 2079             $sent+= strlen($temp);
  2080. 2080             if (is_callable($progressCallback)) {
  2081. 2081                 call_user_func($progressCallback$sent);
  2082. 2082             }
  2083. 2083 
  2084. 2084             $i++;
  2085. 2085 
  2086. 2086             if ($i == NET_SFTP_QUEUE_SIZE) {
  2087. 2087                 if (!$this->_read_put_responses($i)) {
  2088. 2088                     $i 0;
  2089. 2089                     break;
  2090. 2090                 }
  2091. 2091                 $i 0;
  2092. 2092             }
  2093. 2093         }
  2094. 2094 
  2095. 2095         if (!$this->_read_put_responses($i)) {
  2096. 2096             if ($mode NET_SFTP_LOCAL_FILE) {
  2097. 2097                 fclose($fp);
  2098. 2098             }
  2099. 2099             $this->_close_handle($handle);
  2100. 2100             return false;
  2101. 2101         }
  2102. 2102 
  2103. 2103         if ($mode NET_SFTP_LOCAL_FILE) {
  2104. 2104             fclose($fp);
  2105. 2105         }
  2106. 2106 
  2107. 2107         return $this->_close_handle($handle);
  2108. 2108     }
  2109. 2109 
  2110. 2110     /**
  2111. 2111      * Reads multiple successive SSH_FXP_WRITE responses
  2112. 2112      *
  2113. 2113      * Sending an SSH_FXP_WRITE packet and immediately reading its response isn't as efficient as blindly sending out $i
  2114. 2114      * SSH_FXP_WRITEs, in succession, and then reading $i responses.
  2115. 2115      *
  2116. 2116      * @param int $i
  2117. 2117      * @return bool
  2118. 2118      * @access private
  2119. 2119      */
  2120. 2120     function _read_put_responses($i)
  2121. 2121     {
  2122. 2122         while ($i--) {
  2123. 2123             $response $this->_get_sftp_packet();
  2124. 2124             if ($this->packet_type != NET_SFTP_STATUS) {
  2125. 2125                 user_error('Expected SSH_FXP_STATUS');
  2126. 2126                 return false;
  2127. 2127             }
  2128. 2128 
  2129. 2129             if (strlen($response) < 4) {
  2130. 2130                 return false;
  2131. 2131             }
  2132. 2132             extract(unpack('Nstatus'$this->_string_shift($response4)));
  2133. 2133             if ($status != NET_SFTP_STATUS_OK) {
  2134. 2134                 $this->_logError($response$status);
  2135. 2135                 break;
  2136. 2136             }
  2137. 2137         }
  2138. 2138 
  2139. 2139         return $i 0;
  2140. 2140     }
  2141. 2141 
  2142. 2142     /**
  2143. 2143      * Close handle
  2144. 2144      *
  2145. 2145      * @param string $handle
  2146. 2146      * @return bool
  2147. 2147      * @access private
  2148. 2148      */
  2149. 2149     function _close_handle($handle)
  2150. 2150     {
  2151. 2151         if (!$this->_send_sftp_packet(NET_SFTP_CLOSEpack('Na*'strlen($handle), $handle))) {
  2152. 2152             return false;
  2153. 2153         }
  2154. 2154 
  2155. 2155         // "The client MUST release all resources associated with the handle regardless of the status."
  2156. 2156         //  -- http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.1.3
  2157. 2157         $response $this->_get_sftp_packet();
  2158. 2158         if ($this->packet_type != NET_SFTP_STATUS) {
  2159. 2159             user_error('Expected SSH_FXP_STATUS');
  2160. 2160             return false;
  2161. 2161         }
  2162. 2162 
  2163. 2163         if (strlen($response) < 4) {
  2164. 2164             return false;
  2165. 2165         }
  2166. 2166         extract(unpack('Nstatus'$this->_string_shift($response4)));
  2167. 2167         if ($status != NET_SFTP_STATUS_OK) {
  2168. 2168             $this->_logError($response$status);
  2169. 2169             return false;
  2170. 2170         }
  2171. 2171 
  2172. 2172         return true;
  2173. 2173     }
  2174. 2174 
  2175. 2175     /**
  2176. 2176      * Downloads a file from the SFTP server.
  2177. 2177      *
  2178. 2178      * Returns a string containing the contents of $remote_file if $local_file is left undefined or a boolean false if
  2179. 2179      * the operation was unsuccessful.  If $local_file is defined, returns true or false depending on the success of the
  2180. 2180      * operation.
  2181. 2181      *
  2182. 2182      * $offset and $length can be used to download files in chunks.
  2183. 2183      *
  2184. 2184      * @param string $remote_file
  2185. 2185      * @param string $local_file
  2186. 2186      * @param int $offset
  2187. 2187      * @param int $length
  2188. 2188      * @return mixed
  2189. 2189      * @access public
  2190. 2190      */
  2191. 2191     function get($remote_file$local_file false$offset 0$length = -1)
  2192. 2192     {
  2193. 2193         if (!($this->bitmap NET_SSH2_MASK_LOGIN)) {
  2194. 2194             return false;
  2195. 2195         }
  2196. 2196 
  2197. 2197         $remote_file $this->_realpath($remote_file);
  2198. 2198         if ($remote_file === false) {
  2199. 2199             return false;
  2200. 2200         }
  2201. 2201 
  2202. 2202         $packet pack('Na*N2'strlen($remote_file), $remote_fileNET_SFTP_OPEN_READ0);
  2203. 2203         if (!$this->_send_sftp_packet(NET_SFTP_OPEN$packet)) {
  2204. 2204             return false;
  2205. 2205         }
  2206. 2206 
  2207. 2207         $response $this->_get_sftp_packet();
  2208. 2208         switch ($this->packet_type) {
  2209. 2209             case NET_SFTP_HANDLE:
  2210. 2210                 $handle substr($response4);
  2211. 2211                 break;
  2212. 2212             case NET_SFTP_STATUS// presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
  2213. 2213                 $this->_logError($response);
  2214. 2214                 return false;
  2215. 2215             default:
  2216. 2216                 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
  2217. 2217                 return false;
  2218. 2218         }
  2219. 2219 
  2220. 2220         if (is_resource($local_file)) {
  2221. 2221             $fp $local_file;
  2222. 2222             $stat fstat($fp);
  2223. 2223             $res_offset $stat['size'];
  2224. 2224         } else {
  2225. 2225             $res_offset 0;
  2226. 2226             if ($local_file !== false) {
  2227. 2227                 $fp fopen($local_file'wb');
  2228. 2228                 if (!$fp) {
  2229. 2229                     return false;
  2230. 2230                 }
  2231. 2231             } else {
  2232. 2232                 $content '';
  2233. 2233             }
  2234. 2234         }
  2235. 2235 
  2236. 2236         $fclose_check $local_file !== false && !is_resource($local_file);
  2237. 2237 
  2238. 2238         $start $offset;
  2239. 2239         $read 0;
  2240. 2240         while (true) {
  2241. 2241             $i 0;
  2242. 2242 
  2243. 2243             while ($i NET_SFTP_QUEUE_SIZE && ($length || $read $length)) {
  2244. 2244                 $tempoffset $start $read;
  2245. 2245 
  2246. 2246                 $packet_size $length min($this->max_sftp_packet$length $read) : $this->max_sftp_packet;
  2247. 2247 
  2248. 2248                 $packet pack('Na*N3'strlen($handle), $handle$tempoffset 4294967296$tempoffset$packet_size);
  2249. 2249                 if (!$this->_send_sftp_packet(NET_SFTP_READ$packet)) {
  2250. 2250                     if ($fclose_check) {
  2251. 2251                         fclose($fp);
  2252. 2252                     }
  2253. 2253                     return false;
  2254. 2254                 }
  2255. 2255                 $packet null;
  2256. 2256                 $read+= $packet_size;
  2257. 2257                 $i++;
  2258. 2258             }
  2259. 2259 
  2260. 2260             if (!$i) {
  2261. 2261                 break;
  2262. 2262             }
  2263. 2263 
  2264. 2264             $clear_responses false;
  2265. 2265             while ($i 0) {
  2266. 2266                 $i--;
  2267. 2267 
  2268. 2268                 if ($clear_responses) {
  2269. 2269                     $this->_get_sftp_packet();
  2270. 2270                     continue;
  2271. 2271                 } else {
  2272. 2272                     $response $this->_get_sftp_packet();
  2273. 2273                 }
  2274. 2274 
  2275. 2275                 switch ($this->packet_type) {
  2276. 2276                     case NET_SFTP_DATA:
  2277. 2277                         $temp substr($response4);
  2278. 2278                         $offset+= strlen($temp);
  2279. 2279                         if ($local_file === false) {
  2280. 2280                             $content.= $temp;
  2281. 2281                         } else {
  2282. 2282                             fputs($fp$temp);
  2283. 2283                         }
  2284. 2284                         $temp null;
  2285. 2285                         break;
  2286. 2286                     case NET_SFTP_STATUS:
  2287. 2287                         // could, in theory, return false if !strlen($content) but we'll hold off for the time being
  2288. 2288                         $this->_logError($response);
  2289. 2289                         $clear_responses true// don't break out of the loop yet, so we can read the remaining responses
  2290. 2290                         break;
  2291. 2291                     default:
  2292. 2292                         if ($fclose_check) {
  2293. 2293                             fclose($fp);
  2294. 2294                         }
  2295. 2295                         user_error('Expected SSH_FX_DATA or SSH_FXP_STATUS');
  2296. 2296                 }
  2297. 2297                 $response null;
  2298. 2298             }
  2299. 2299 
  2300. 2300             if ($clear_responses) {
  2301. 2301                 break;
  2302. 2302             }
  2303. 2303         }
  2304. 2304 
  2305. 2305         if ($length && $length <= $offset $start) {
  2306. 2306             if ($local_file === false) {
  2307. 2307                 $content substr($content0$length);
  2308. 2308             } else {
  2309. 2309                 ftruncate($fp$length $res_offset);
  2310. 2310             }
  2311. 2311         }
  2312. 2312 
  2313. 2313         if ($fclose_check) {
  2314. 2314             fclose($fp);
  2315. 2315         }
  2316. 2316 
  2317. 2317         if (!$this->_close_handle($handle)) {
  2318. 2318             return false;
  2319. 2319         }
  2320. 2320 
  2321. 2321         // if $content isn't set that means a file was written to
  2322. 2322         return isset($content) ? $content true;
  2323. 2323     }
  2324. 2324 
  2325. 2325     /**
  2326. 2326      * Deletes a file on the SFTP server.
  2327. 2327      *
  2328. 2328      * @param string $path
  2329. 2329      * @param bool $recursive
  2330. 2330      * @return bool
  2331. 2331      * @access public
  2332. 2332      */
  2333. 2333     function delete($path$recursive true)
  2334. 2334     {
  2335. 2335         if (!($this->bitmap NET_SSH2_MASK_LOGIN)) {
  2336. 2336             return false;
  2337. 2337         }
  2338. 2338 
  2339. 2339         if (is_object($path)) {
  2340. 2340             // It's an object. Cast it as string before we check anything else.
  2341. 2341             $path = (string) $path;
  2342. 2342         }
  2343. 2343 
  2344. 2344         if (!is_string($path) || $path == '') {
  2345. 2345             return false;
  2346. 2346         }
  2347. 2347 
  2348. 2348         $path $this->_realpath($path);
  2349. 2349         if ($path === false) {
  2350. 2350             return false;
  2351. 2351         }
  2352. 2352 
  2353. 2353         // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
  2354. 2354         if (!$this->_send_sftp_packet(NET_SFTP_REMOVEpack('Na*'strlen($path), $path))) {
  2355. 2355             return false;
  2356. 2356         }
  2357. 2357 
  2358. 2358         $response $this->_get_sftp_packet();
  2359. 2359         if ($this->packet_type != NET_SFTP_STATUS) {
  2360. 2360             user_error('Expected SSH_FXP_STATUS');
  2361. 2361             return false;
  2362. 2362         }
  2363. 2363 
  2364. 2364         // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
  2365. 2365         if (strlen($response) < 4) {
  2366. 2366             return false;
  2367. 2367         }
  2368. 2368         extract(unpack('Nstatus'$this->_string_shift($response4)));
  2369. 2369         if ($status != NET_SFTP_STATUS_OK) {
  2370. 2370             $this->_logError($response$status);
  2371. 2371             if (!$recursive) {
  2372. 2372                 return false;
  2373. 2373             }
  2374. 2374             $i 0;
  2375. 2375             $result $this->_delete_recursive($path$i);
  2376. 2376             $this->_read_put_responses($i);
  2377. 2377             return $result;
  2378. 2378         }
  2379. 2379 
  2380. 2380         $this->_remove_from_stat_cache($path);
  2381. 2381 
  2382. 2382         return true;
  2383. 2383     }
  2384. 2384 
  2385. 2385     /**
  2386. 2386      * Recursively deletes directories on the SFTP server
  2387. 2387      *
  2388. 2388      * Minimizes directory lookups and SSH_FXP_STATUS requests for speed.
  2389. 2389      *
  2390. 2390      * @param string $path
  2391. 2391      * @param int $i
  2392. 2392      * @return bool
  2393. 2393      * @access private
  2394. 2394      */
  2395. 2395     function _delete_recursive($path, &$i)
  2396. 2396     {
  2397. 2397         if (!$this->_read_put_responses($i)) {
  2398. 2398             return false;
  2399. 2399         }
  2400. 2400         $i 0;
  2401. 2401         $entries $this->_list($pathtrue);
  2402. 2402 
  2403. 2403         // normally $entries would have at least . and .. but it might not if the directories
  2404. 2404         // permissions didn't allow reading
  2405. 2405         if (empty($entries)) {
  2406. 2406             return false;
  2407. 2407         }
  2408. 2408 
  2409. 2409         unset($entries['.'], $entries['..']);
  2410. 2410         foreach ($entries as $filename => $props) {
  2411. 2411             if (!isset($props['type'])) {
  2412. 2412                 return false;
  2413. 2413             }
  2414. 2414 
  2415. 2415             $temp $path '/' $filename;
  2416. 2416             if ($props['type'] == NET_SFTP_TYPE_DIRECTORY) {
  2417. 2417                 if (!$this->_delete_recursive($temp$i)) {
  2418. 2418                     return false;
  2419. 2419                 }
  2420. 2420             } else {
  2421. 2421                 if (!$this->_send_sftp_packet(NET_SFTP_REMOVEpack('Na*'strlen($temp), $temp))) {
  2422. 2422                     return false;
  2423. 2423                 }
  2424. 2424                 $this->_remove_from_stat_cache($temp);
  2425. 2425 
  2426. 2426                 $i++;
  2427. 2427 
  2428. 2428                 if ($i >= NET_SFTP_QUEUE_SIZE) {
  2429. 2429                     if (!$this->_read_put_responses($i)) {
  2430. 2430                         return false;
  2431. 2431                     }
  2432. 2432                     $i 0;
  2433. 2433                 }
  2434. 2434             }
  2435. 2435         }
  2436. 2436 
  2437. 2437         if (!$this->_send_sftp_packet(NET_SFTP_RMDIRpack('Na*'strlen($path), $path))) {
  2438. 2438             return false;
  2439. 2439         }
  2440. 2440         $this->_remove_from_stat_cache($path);
  2441. 2441 
  2442. 2442         $i++;
  2443. 2443 
  2444. 2444         if ($i >= NET_SFTP_QUEUE_SIZE) {
  2445. 2445             if (!$this->_read_put_responses($i)) {
  2446. 2446                 return false;
  2447. 2447             }
  2448. 2448             $i 0;
  2449. 2449         }
  2450. 2450 
  2451. 2451         return true;
  2452. 2452     }
  2453. 2453 
  2454. 2454     /**
  2455. 2455      * Checks whether a file or directory exists
  2456. 2456      *
  2457. 2457      * @param string $path
  2458. 2458      * @return bool
  2459. 2459      * @access public
  2460. 2460      */
  2461. 2461     function file_exists($path)
  2462. 2462     {
  2463. 2463         if ($this->use_stat_cache) {
  2464. 2464             $path $this->_realpath($path);
  2465. 2465 
  2466. 2466             $result $this->_query_stat_cache($path);
  2467. 2467 
  2468. 2468             if (isset($result)) {
  2469. 2469                 // return true if $result is an array or if it's an stdClass object
  2470. 2470                 return $result !== false;
  2471. 2471             }
  2472. 2472         }
  2473. 2473 
  2474. 2474         return $this->stat($path) !== false;
  2475. 2475     }
  2476. 2476 
  2477. 2477     /**
  2478. 2478      * Tells whether the filename is a directory
  2479. 2479      *
  2480. 2480      * @param string $path
  2481. 2481      * @return bool
  2482. 2482      * @access public
  2483. 2483      */
  2484. 2484     function is_dir($path)
  2485. 2485     {
  2486. 2486         $result $this->_get_stat_cache_prop($path'type');
  2487. 2487         if ($result === false) {
  2488. 2488             return false;
  2489. 2489         }
  2490. 2490         return $result === NET_SFTP_TYPE_DIRECTORY;
  2491. 2491     }
  2492. 2492 
  2493. 2493     /**
  2494. 2494      * Tells whether the filename is a regular file
  2495. 2495      *
  2496. 2496      * @param string $path
  2497. 2497      * @return bool
  2498. 2498      * @access public
  2499. 2499      */
  2500. 2500     function is_file($path)
  2501. 2501     {
  2502. 2502         $result $this->_get_stat_cache_prop($path'type');
  2503. 2503         if ($result === false) {
  2504. 2504             return false;
  2505. 2505         }
  2506. 2506         return $result === NET_SFTP_TYPE_REGULAR;
  2507. 2507     }
  2508. 2508 
  2509. 2509     /**
  2510. 2510      * Tells whether the filename is a symbolic link
  2511. 2511      *
  2512. 2512      * @param string $path
  2513. 2513      * @return bool
  2514. 2514      * @access public
  2515. 2515      */
  2516. 2516     function is_link($path)
  2517. 2517     {
  2518. 2518         $result $this->_get_lstat_cache_prop($path'type');
  2519. 2519         if ($result === false) {
  2520. 2520             return false;
  2521. 2521         }
  2522. 2522         return $result === NET_SFTP_TYPE_SYMLINK;
  2523. 2523     }
  2524. 2524 
  2525. 2525     /**
  2526. 2526      * Tells whether a file exists and is readable
  2527. 2527      *
  2528. 2528      * @param string $path
  2529. 2529      * @return bool
  2530. 2530      * @access public
  2531. 2531      */
  2532. 2532     function is_readable($path)
  2533. 2533     {
  2534. 2534         $path $this->_realpath($path);
  2535. 2535 
  2536. 2536         $packet pack('Na*N2'strlen($path), $pathNET_SFTP_OPEN_READ0);
  2537. 2537         if (!$this->_send_sftp_packet(NET_SFTP_OPEN$packet)) {
  2538. 2538             return false;
  2539. 2539         }
  2540. 2540 
  2541. 2541         $response $this->_get_sftp_packet();
  2542. 2542         switch ($this->packet_type) {
  2543. 2543             case NET_SFTP_HANDLE:
  2544. 2544                 return true;
  2545. 2545             case NET_SFTP_STATUS// presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
  2546. 2546                 return false;
  2547. 2547             default:
  2548. 2548                 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
  2549. 2549                 return false;
  2550. 2550         }
  2551. 2551     }
  2552. 2552 
  2553. 2553     /**
  2554. 2554      * Tells whether the filename is writable
  2555. 2555      *
  2556. 2556      * @param string $path
  2557. 2557      * @return bool
  2558. 2558      * @access public
  2559. 2559      */
  2560. 2560     function is_writable($path)
  2561. 2561     {
  2562. 2562         $path $this->_realpath($path);
  2563. 2563 
  2564. 2564         $packet pack('Na*N2'strlen($path), $pathNET_SFTP_OPEN_WRITE0);
  2565. 2565         if (!$this->_send_sftp_packet(NET_SFTP_OPEN$packet)) {
  2566. 2566             return false;
  2567. 2567         }
  2568. 2568 
  2569. 2569         $response $this->_get_sftp_packet();
  2570. 2570         switch ($this->packet_type) {
  2571. 2571             case NET_SFTP_HANDLE:
  2572. 2572                 return true;
  2573. 2573             case NET_SFTP_STATUS// presumably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
  2574. 2574                 return false;
  2575. 2575             default:
  2576. 2576                 user_error('Expected SSH_FXP_HANDLE or SSH_FXP_STATUS');
  2577. 2577                 return false;
  2578. 2578         }
  2579. 2579     }
  2580. 2580 
  2581. 2581     /**
  2582. 2582      * Tells whether the filename is writeable
  2583. 2583      *
  2584. 2584      * Alias of is_writable
  2585. 2585      *
  2586. 2586      * @param string $path
  2587. 2587      * @return bool
  2588. 2588      * @access public
  2589. 2589      */
  2590. 2590     function is_writeable($path)
  2591. 2591     {
  2592. 2592         return $this->is_writable($path);
  2593. 2593     }
  2594. 2594 
  2595. 2595     /**
  2596. 2596      * Gets last access time of file
  2597. 2597      *
  2598. 2598      * @param string $path
  2599. 2599      * @return mixed
  2600. 2600      * @access public
  2601. 2601      */
  2602. 2602     function fileatime($path)
  2603. 2603     {
  2604. 2604         return $this->_get_stat_cache_prop($path'atime');
  2605. 2605     }
  2606. 2606 
  2607. 2607     /**
  2608. 2608      * Gets file modification time
  2609. 2609      *
  2610. 2610      * @param string $path
  2611. 2611      * @return mixed
  2612. 2612      * @access public
  2613. 2613      */
  2614. 2614     function filemtime($path)
  2615. 2615     {
  2616. 2616         return $this->_get_stat_cache_prop($path'mtime');
  2617. 2617     }
  2618. 2618 
  2619. 2619     /**
  2620. 2620      * Gets file permissions
  2621. 2621      *
  2622. 2622      * @param string $path
  2623. 2623      * @return mixed
  2624. 2624      * @access public
  2625. 2625      */
  2626. 2626     function fileperms($path)
  2627. 2627     {
  2628. 2628         return $this->_get_stat_cache_prop($path'permissions');
  2629. 2629     }
  2630. 2630 
  2631. 2631     /**
  2632. 2632      * Gets file owner
  2633. 2633      *
  2634. 2634      * @param string $path
  2635. 2635      * @return mixed
  2636. 2636      * @access public
  2637. 2637      */
  2638. 2638     function fileowner($path)
  2639. 2639     {
  2640. 2640         return $this->_get_stat_cache_prop($path'uid');
  2641. 2641     }
  2642. 2642 
  2643. 2643     /**
  2644. 2644      * Gets file group
  2645. 2645      *
  2646. 2646      * @param string $path
  2647. 2647      * @return mixed
  2648. 2648      * @access public
  2649. 2649      */
  2650. 2650     function filegroup($path)
  2651. 2651     {
  2652. 2652         return $this->_get_stat_cache_prop($path'gid');
  2653. 2653     }
  2654. 2654 
  2655. 2655     /**
  2656. 2656      * Gets file size
  2657. 2657      *
  2658. 2658      * @param string $path
  2659. 2659      * @return mixed
  2660. 2660      * @access public
  2661. 2661      */
  2662. 2662     function filesize($path)
  2663. 2663     {
  2664. 2664         return $this->_get_stat_cache_prop($path'size');
  2665. 2665     }
  2666. 2666 
  2667. 2667     /**
  2668. 2668      * Gets file type
  2669. 2669      *
  2670. 2670      * @param string $path
  2671. 2671      * @return mixed
  2672. 2672      * @access public
  2673. 2673      */
  2674. 2674     function filetype($path)
  2675. 2675     {
  2676. 2676         $type $this->_get_stat_cache_prop($path'type');
  2677. 2677         if ($type === false) {
  2678. 2678             return false;
  2679. 2679         }
  2680. 2680 
  2681. 2681         switch ($type) {
  2682. 2682             case NET_SFTP_TYPE_BLOCK_DEVICE:
  2683. 2683                 return 'block';
  2684. 2684             case NET_SFTP_TYPE_CHAR_DEVICE:
  2685. 2685                 return 'char';
  2686. 2686             case NET_SFTP_TYPE_DIRECTORY:
  2687. 2687                 return 'dir';
  2688. 2688             case NET_SFTP_TYPE_FIFO:
  2689. 2689                 return 'fifo';
  2690. 2690             case NET_SFTP_TYPE_REGULAR:
  2691. 2691                 return 'file';
  2692. 2692             case NET_SFTP_TYPE_SYMLINK:
  2693. 2693                 return 'link';
  2694. 2694             default:
  2695. 2695                 return false;
  2696. 2696         }
  2697. 2697     }
  2698. 2698 
  2699. 2699     /**
  2700. 2700      * Return a stat properity
  2701. 2701      *
  2702. 2702      * Uses cache if appropriate.
  2703. 2703      *
  2704. 2704      * @param string $path
  2705. 2705      * @param string $prop
  2706. 2706      * @return mixed
  2707. 2707      * @access private
  2708. 2708      */
  2709. 2709     function _get_stat_cache_prop($path$prop)
  2710. 2710     {
  2711. 2711         return $this->_get_xstat_cache_prop($path$prop'stat');
  2712. 2712     }
  2713. 2713 
  2714. 2714     /**
  2715. 2715      * Return an lstat properity
  2716. 2716      *
  2717. 2717      * Uses cache if appropriate.
  2718. 2718      *
  2719. 2719      * @param string $path
  2720. 2720      * @param string $prop
  2721. 2721      * @return mixed
  2722. 2722      * @access private
  2723. 2723      */
  2724. 2724     function _get_lstat_cache_prop($path$prop)
  2725. 2725     {
  2726. 2726         return $this->_get_xstat_cache_prop($path$prop'lstat');
  2727. 2727     }
  2728. 2728 
  2729. 2729     /**
  2730. 2730      * Return a stat or lstat properity
  2731. 2731      *
  2732. 2732      * Uses cache if appropriate.
  2733. 2733      *
  2734. 2734      * @param string $path
  2735. 2735      * @param string $prop
  2736. 2736      * @return mixed
  2737. 2737      * @access private
  2738. 2738      */
  2739. 2739     function _get_xstat_cache_prop($path$prop$type)
  2740. 2740     {
  2741. 2741         if ($this->use_stat_cache) {
  2742. 2742             $path $this->_realpath($path);
  2743. 2743 
  2744. 2744             $result $this->_query_stat_cache($path);
  2745. 2745 
  2746. 2746             if (is_object($result) && isset($result->$type)) {
  2747. 2747                 return $result->{$type}[$prop];
  2748. 2748             }
  2749. 2749         }
  2750. 2750 
  2751. 2751         $result $this->$type($path);
  2752. 2752 
  2753. 2753         if ($result === false || !isset($result[$prop])) {
  2754. 2754             return false;
  2755. 2755         }
  2756. 2756 
  2757. 2757         return $result[$prop];
  2758. 2758     }
  2759. 2759 
  2760. 2760     /**
  2761. 2761      * Renames a file or a directory on the SFTP server
  2762. 2762      *
  2763. 2763      * @param string $oldname
  2764. 2764      * @param string $newname
  2765. 2765      * @return bool
  2766. 2766      * @access public
  2767. 2767      */
  2768. 2768     function rename($oldname$newname)
  2769. 2769     {
  2770. 2770         if (!($this->bitmap NET_SSH2_MASK_LOGIN)) {
  2771. 2771             return false;
  2772. 2772         }
  2773. 2773 
  2774. 2774         $oldname $this->_realpath($oldname);
  2775. 2775         $newname $this->_realpath($newname);
  2776. 2776         if ($oldname === false || $newname === false) {
  2777. 2777             return false;
  2778. 2778         }
  2779. 2779 
  2780. 2780         // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-8.3
  2781. 2781         $packet pack('Na*Na*'strlen($oldname), $oldnamestrlen($newname), $newname);
  2782. 2782         if (!$this->_send_sftp_packet(NET_SFTP_RENAME$packet)) {
  2783. 2783             return false;
  2784. 2784         }
  2785. 2785 
  2786. 2786         $response $this->_get_sftp_packet();
  2787. 2787         if ($this->packet_type != NET_SFTP_STATUS) {
  2788. 2788             user_error('Expected SSH_FXP_STATUS');
  2789. 2789             return false;
  2790. 2790         }
  2791. 2791 
  2792. 2792         // if $status isn't SSH_FX_OK it's probably SSH_FX_NO_SUCH_FILE or SSH_FX_PERMISSION_DENIED
  2793. 2793         if (strlen($response) < 4) {
  2794. 2794             return false;
  2795. 2795         }
  2796. 2796         extract(unpack('Nstatus'$this->_string_shift($response4)));
  2797. 2797         if ($status != NET_SFTP_STATUS_OK) {
  2798. 2798             $this->_logError($response$status);
  2799. 2799             return false;
  2800. 2800         }
  2801. 2801 
  2802. 2802         // don't move the stat cache entry over since this operation could very well change the
  2803. 2803         // atime and mtime attributes
  2804. 2804         //$this->_update_stat_cache($newname, $this->_query_stat_cache($oldname));
  2805. 2805         $this->_remove_from_stat_cache($oldname);
  2806. 2806         $this->_remove_from_stat_cache($newname);
  2807. 2807 
  2808. 2808         return true;
  2809. 2809     }
  2810. 2810 
  2811. 2811     /**
  2812. 2812      * Parse Attributes
  2813. 2813      *
  2814. 2814      * See '7.  File Attributes' of draft-ietf-secsh-filexfer-13 for more info.
  2815. 2815      *
  2816. 2816      * @param string $response
  2817. 2817      * @return array
  2818. 2818      * @access private
  2819. 2819      */
  2820. 2820     function _parseAttributes(&$response)
  2821. 2821     {
  2822. 2822         $attr = array();
  2823. 2823         if (strlen($response) < 4) {
  2824. 2824             user_error('Malformed file attributes');
  2825. 2825             return array();
  2826. 2826         }
  2827. 2827         extract(unpack('Nflags'$this->_string_shift($response4)));
  2828. 2828         // SFTPv4+ have a type field (a byte) that follows the above flag field
  2829. 2829         foreach ($this->attributes as $key => $value) {
  2830. 2830             switch ($flags $key) {
  2831. 2831                 case NET_SFTP_ATTR_SIZE// 0x00000001
  2832. 2832                     // The size attribute is defined as an unsigned 64-bit integer.
  2833. 2833                     // The following will use floats on 32-bit platforms, if necessary.
  2834. 2834                     // As can be seen in the BigInteger class, floats are generally
  2835. 2835                     // IEEE 754 binary64 "double precision" on such platforms and
  2836. 2836                     // as such can represent integers of at least 2^50 without loss
  2837. 2837                     // of precision. Interpreted in filesize, 2^50 bytes = 1024 TiB.
  2838. 2838                     $attr['size'] = hexdec(bin2hex($this->_string_shift($response8)));
  2839. 2839                     break;
  2840. 2840                 case NET_SFTP_ATTR_UIDGID// 0x00000002 (SFTPv3 only)
  2841. 2841                     if (strlen($response) < 8) {
  2842. 2842                         user_error('Malformed file attributes');
  2843. 2843                         return $attr;
  2844. 2844                     }
  2845. 2845                     $attr+= unpack('Nuid/Ngid'$this->_string_shift($response8));
  2846. 2846                     break;
  2847. 2847                 case NET_SFTP_ATTR_PERMISSIONS// 0x00000004
  2848. 2848                     if (strlen($response) < 4) {
  2849. 2849                         user_error('Malformed file attributes');
  2850. 2850                         return $attr;
  2851. 2851                     }
  2852. 2852                     $attr+= unpack('Npermissions'$this->_string_shift($response4));
  2853. 2853                     // mode == permissions; permissions was the original array key and is retained for bc purposes.
  2854. 2854                     // mode was added because that's the more industry standard terminology
  2855. 2855                     $attr+= array('mode' => $attr['permissions']);
  2856. 2856                     $fileType $this->_parseMode($attr['permissions']);
  2857. 2857                     if ($fileType !== false) {
  2858. 2858                         $attr+= array('type' => $fileType);
  2859. 2859                     }
  2860. 2860                     break;
  2861. 2861                 case NET_SFTP_ATTR_ACCESSTIME// 0x00000008
  2862. 2862                     if (strlen($response) < 8) {
  2863. 2863                         user_error('Malformed file attributes');
  2864. 2864                         return $attr;
  2865. 2865                     }
  2866. 2866                     $attr+= unpack('Natime/Nmtime'$this->_string_shift($response8));
  2867. 2867                     break;
  2868. 2868                 case NET_SFTP_ATTR_EXTENDED// 0x80000000
  2869. 2869                     if (strlen($response) < 4) {
  2870. 2870                         user_error('Malformed file attributes');
  2871. 2871                         return $attr;
  2872. 2872                     }
  2873. 2873                     extract(unpack('Ncount'$this->_string_shift($response4)));
  2874. 2874                     for ($i 0$i $count$i++) {
  2875. 2875                         if (strlen($response) < 4) {
  2876. 2876                             user_error('Malformed file attributes');
  2877. 2877                             return $attr;
  2878. 2878                         }
  2879. 2879                         extract(unpack('Nlength'$this->_string_shift($response4)));
  2880. 2880                         $key $this->_string_shift($response$length);
  2881. 2881                         if (strlen($response) < 4) {
  2882. 2882                             user_error('Malformed file attributes');
  2883. 2883                             return $attr;
  2884. 2884                         }
  2885. 2885                         extract(unpack('Nlength'$this->_string_shift($response4)));
  2886. 2886                         $attr[$key] = $this->_string_shift($response$length);
  2887. 2887                     }
  2888. 2888             }
  2889. 2889         }
  2890. 2890         return $attr;
  2891. 2891     }
  2892. 2892 
  2893. 2893     /**
  2894. 2894      * Attempt to identify the file type
  2895. 2895      *
  2896. 2896      * Quoting the SFTP RFC, "Implementations MUST NOT send bits that are not defined" but they seem to anyway
  2897. 2897      *
  2898. 2898      * @param int $mode
  2899. 2899      * @return int
  2900. 2900      * @access private
  2901. 2901      */
  2902. 2902     function _parseMode($mode)
  2903. 2903     {
  2904. 2904         // values come from http://lxr.free-electrons.com/source/include/uapi/linux/stat.h#L12
  2905. 2905         // see, also, http://linux.die.net/man/2/stat
  2906. 2906         switch ($mode 0170000) {// ie. 1111 0000 0000 0000
  2907. 2907             case 0000000// no file type specified - figure out the file type using alternative means
  2908. 2908                 return false;
  2909. 2909             case 0040000:
  2910. 2910                 return NET_SFTP_TYPE_DIRECTORY;
  2911. 2911             case 0100000:
  2912. 2912                 return NET_SFTP_TYPE_REGULAR;
  2913. 2913             case 0120000:
  2914. 2914                 return NET_SFTP_TYPE_SYMLINK;
  2915. 2915             // new types introduced in SFTPv5+
  2916. 2916             // http://tools.ietf.org/html/draft-ietf-secsh-filexfer-05#section-5.2
  2917. 2917             case 0010000// named pipe (fifo)
  2918. 2918                 return NET_SFTP_TYPE_FIFO;
  2919. 2919             case 0020000// character special
  2920. 2920                 return NET_SFTP_TYPE_CHAR_DEVICE;
  2921. 2921             case 0060000// block special
  2922. 2922                 return NET_SFTP_TYPE_BLOCK_DEVICE;
  2923. 2923             case 0140000// socket
  2924. 2924                 return NET_SFTP_TYPE_SOCKET;
  2925. 2925             case 0160000// whiteout
  2926. 2926                 // "SPECIAL should be used for files that are of
  2927. 2927                 //  a known type which cannot be expressed in the protocol"
  2928. 2928                 return NET_SFTP_TYPE_SPECIAL;
  2929. 2929             default:
  2930. 2930                 return NET_SFTP_TYPE_UNKNOWN;
  2931. 2931         }
  2932. 2932     }
  2933. 2933 
  2934. 2934     /**
  2935. 2935      * Parse Longname
  2936. 2936      *
  2937. 2937      * SFTPv3 doesn't provide any easy way of identifying a file type.  You could try to open
  2938. 2938      * a file as a directory and see if an error is returned or you could try to parse the
  2939. 2939      * SFTPv3-specific longname field of the SSH_FXP_NAME packet.  That's what this function does.
  2940. 2940      * The result is returned using the
  2941. 2941      * {@link http://tools.ietf.org/html/draft-ietf-secsh-filexfer-04#section-5.2 SFTPv4 type constants}.
  2942. 2942      *
  2943. 2943      * If the longname is in an unrecognized format bool(false) is returned.
  2944. 2944      *
  2945. 2945      * @param string $longname
  2946. 2946      * @return mixed
  2947. 2947      * @access private
  2948. 2948      */
  2949. 2949     function _parseLongname($longname)
  2950. 2950     {
  2951. 2951         // http://en.wikipedia.org/wiki/Unix_file_types
  2952. 2952         // http://en.wikipedia.org/wiki/Filesystem_permissions#Notation_of_traditional_Unix_permissions
  2953. 2953         if (preg_match('#^[^/]([r-][w-][xstST-]){3}#'$longname)) {
  2954. 2954             switch ($longname[0]) {
  2955. 2955                 case '-':
  2956. 2956                     return NET_SFTP_TYPE_REGULAR;
  2957. 2957                 case 'd':
  2958. 2958                     return NET_SFTP_TYPE_DIRECTORY;
  2959. 2959                 case 'l':
  2960. 2960                     return NET_SFTP_TYPE_SYMLINK;
  2961. 2961                 default:
  2962. 2962                     return NET_SFTP_TYPE_SPECIAL;
  2963. 2963             }
  2964. 2964         }
  2965. 2965 
  2966. 2966         return false;
  2967. 2967     }
  2968. 2968 
  2969. 2969     /**
  2970. 2970      * Sends SFTP Packets
  2971. 2971      *
  2972. 2972      * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
  2973. 2973      *
  2974. 2974      * @param int $type
  2975. 2975      * @param string $data
  2976. 2976      * @see self::_get_sftp_packet()
  2977. 2977      * @see Net_SSH2::_send_channel_packet()
  2978. 2978      * @return bool
  2979. 2979      * @access private
  2980. 2980      */
  2981. 2981     function _send_sftp_packet($type$data)
  2982. 2982     {
  2983. 2983         $packet $this->request_id !== false ?
  2984. 2984             pack('NCNa*'strlen($data) + 5$type$this->request_id$data) :
  2985. 2985             pack('NCa*'strlen($data) + 1$type$data);
  2986. 2986 
  2987. 2987         $start strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
  2988. 2988         $result $this->_send_channel_packet(NET_SFTP_CHANNEL$packet);
  2989. 2989         $stop strtok(microtime(), ' ') + strtok('');
  2990. 2990 
  2991. 2991         if (defined('NET_SFTP_LOGGING')) {
  2992. 2992             $packet_type '-> ' $this->packet_types[$type] .
  2993. 2993                            ' (' round($stop $start4) . 's)';
  2994. 2994             if (NET_SFTP_LOGGING == NET_SFTP_LOG_REALTIME) {
  2995. 2995                 echo "<pre>\r\n" $this->_format_log(array($data), array($packet_type)) . "\r\n</pre>\r\n";
  2996. 2996                 flush();
  2997. 2997                 ob_flush();
  2998. 2998             } else {
  2999. 2999                 $this->packet_type_log[] = $packet_type;
  3000. 3000                 if (NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX) {
  3001. 3001                     $this->packet_log[] = $data;
  3002. 3002                 }
  3003. 3003             }
  3004. 3004         }
  3005. 3005 
  3006. 3006         return $result;
  3007. 3007     }
  3008. 3008 
  3009. 3009     /**
  3010. 3010      * Receives SFTP Packets
  3011. 3011      *
  3012. 3012      * See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
  3013. 3013      *
  3014. 3014      * Incidentally, the number of SSH_MSG_CHANNEL_DATA messages has no bearing on the number of SFTP packets present.
  3015. 3015      * There can be one SSH_MSG_CHANNEL_DATA messages containing two SFTP packets or there can be two SSH_MSG_CHANNEL_DATA
  3016. 3016      * messages containing one SFTP packet.
  3017. 3017      *
  3018. 3018      * @see self::_send_sftp_packet()
  3019. 3019      * @return string
  3020. 3020      * @access private
  3021. 3021      */
  3022. 3022     function _get_sftp_packet()
  3023. 3023     {
  3024. 3024         $this->curTimeout false;
  3025. 3025 
  3026. 3026         $start strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
  3027. 3027 
  3028. 3028         // SFTP packet length
  3029. 3029         while (strlen($this->packet_buffer) < 4) {
  3030. 3030             $temp $this->_get_channel_packet(NET_SFTP_CHANNELtrue);
  3031. 3031             if (is_bool($temp)) {
  3032. 3032                 $this->packet_type false;
  3033. 3033                 $this->packet_buffer '';
  3034. 3034                 return false;
  3035. 3035             }
  3036. 3036             $this->packet_buffer.= $temp;
  3037. 3037         }
  3038. 3038         if (strlen($this->packet_buffer) < 4) {
  3039. 3039             return false;
  3040. 3040         }
  3041. 3041         extract(unpack('Nlength'$this->_string_shift($this->packet_buffer4)));
  3042. 3042         $tempLength $length;
  3043. 3043         $tempLength-= strlen($this->packet_buffer);
  3044. 3044 
  3045. 3045         // SFTP packet type and data payload
  3046. 3046         while ($tempLength 0) {
  3047. 3047             $temp $this->_get_channel_packet(NET_SFTP_CHANNELtrue);
  3048. 3048             if (is_bool($temp)) {
  3049. 3049                 $this->packet_type false;
  3050. 3050                 $this->packet_buffer '';
  3051. 3051                 return false;
  3052. 3052             }
  3053. 3053             $this->packet_buffer.= $temp;
  3054. 3054             $tempLength-= strlen($temp);
  3055. 3055         }
  3056. 3056 
  3057. 3057         $stop strtok(microtime(), ' ') + strtok('');
  3058. 3058 
  3059. 3059         $this->packet_type ord($this->_string_shift($this->packet_buffer));
  3060. 3060 
  3061. 3061         if ($this->request_id !== false) {
  3062. 3062             $this->_string_shift($this->packet_buffer4); // remove the request id
  3063. 3063             $length-= 5// account for the request id and the packet type
  3064. 3064         } else {
  3065. 3065             $length-= 1// account for the packet type
  3066. 3066         }
  3067. 3067 
  3068. 3068         $packet $this->_string_shift($this->packet_buffer$length);
  3069. 3069 
  3070. 3070         if (defined('NET_SFTP_LOGGING')) {
  3071. 3071             $packet_type '<- ' $this->packet_types[$this->packet_type] .
  3072. 3072                            ' (' round($stop $start4) . 's)';
  3073. 3073             if (NET_SFTP_LOGGING == NET_SFTP_LOG_REALTIME) {
  3074. 3074                 echo "<pre>\r\n" $this->_format_log(array($packet), array($packet_type)) . "\r\n</pre>\r\n";
  3075. 3075                 flush();
  3076. 3076                 ob_flush();
  3077. 3077             } else {
  3078. 3078                 $this->packet_type_log[] = $packet_type;
  3079. 3079                 if (NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX) {
  3080. 3080                     $this->packet_log[] = $packet;
  3081. 3081                 }
  3082. 3082             }
  3083. 3083         }
  3084. 3084 
  3085. 3085         return $packet;
  3086. 3086     }
  3087. 3087 
  3088. 3088     /**
  3089. 3089      * Returns a log of the packets that have been sent and received.
  3090. 3090      *
  3091. 3091      * Returns a string if NET_SFTP_LOGGING == NET_SFTP_LOG_COMPLEX, an array if NET_SFTP_LOGGING == NET_SFTP_LOG_SIMPLE and false if !defined('NET_SFTP_LOGGING')
  3092. 3092      *
  3093. 3093      * @access public
  3094. 3094      * @return string or Array
  3095. 3095      */
  3096. 3096     function getSFTPLog()
  3097. 3097     {
  3098. 3098         if (!defined('NET_SFTP_LOGGING')) {
  3099. 3099             return false;
  3100. 3100         }
  3101. 3101 
  3102. 3102         switch (NET_SFTP_LOGGING) {
  3103. 3103             case NET_SFTP_LOG_COMPLEX:
  3104. 3104                 return $this->_format_log($this->packet_log$this->packet_type_log);
  3105. 3105                 break;
  3106. 3106             //case NET_SFTP_LOG_SIMPLE:
  3107. 3107             default:
  3108. 3108                 return $this->packet_type_log;
  3109. 3109         }
  3110. 3110     }
  3111. 3111 
  3112. 3112     /**
  3113. 3113      * Returns all errors
  3114. 3114      *
  3115. 3115      * @return array
  3116. 3116      * @access public
  3117. 3117      */
  3118. 3118     function getSFTPErrors()
  3119. 3119     {
  3120. 3120         return $this->sftp_errors;
  3121. 3121     }
  3122. 3122 
  3123. 3123     /**
  3124. 3124      * Returns the last error
  3125. 3125      *
  3126. 3126      * @return string
  3127. 3127      * @access public
  3128. 3128      */
  3129. 3129     function getLastSFTPError()
  3130. 3130     {
  3131. 3131         return count($this->sftp_errors) ? $this->sftp_errors[count($this->sftp_errors) - 1] : '';
  3132. 3132     }
  3133. 3133 
  3134. 3134     /**
  3135. 3135      * Get supported SFTP versions
  3136. 3136      *
  3137. 3137      * @return array
  3138. 3138      * @access public
  3139. 3139      */
  3140. 3140     function getSupportedVersions()
  3141. 3141     {
  3142. 3142         $temp = array('version' => $this->version);
  3143. 3143         if (isset($this->extensions['versions'])) {
  3144. 3144             $temp['extensions'] = $this->extensions['versions'];
  3145. 3145         }
  3146. 3146         return $temp;
  3147. 3147     }
  3148. 3148 
  3149. 3149     /**
  3150. 3150      * Disconnect
  3151. 3151      *
  3152. 3152      * @param int $reason
  3153. 3153      * @return bool
  3154. 3154      * @access private
  3155. 3155      */
  3156. 3156     function _disconnect($reason)
  3157. 3157     {
  3158. 3158         $this->pwd false;
  3159. 3159         parent::_disconnect($reason);
  3160. 3160     }
  3161. 3161 }